This guide covers deploying a single Prebid Sales Agent instance that serves multiple publishers, each isolated in their own tenant with separate data, credentials, and configuration.
Multi-tenant mode is designed for organizations that manage ad operations on behalf of multiple publishers. Typical use cases include:
In multi-tenant mode, each publisher gets an isolated tenant with its own subdomain, data, ad server credentials, and user access. A single deployment handles all tenants, reducing infrastructure overhead.
If you are deploying for a single publisher, see the Single-Tenant Deployment guide instead.
Multi-tenant mode adds a tenant resolution layer on top of the standard Sales Agent architecture. Incoming requests are routed to the correct tenant based on the subdomain in the request:
┌──────────────────────────────────────────────────────────────┐
│ DNS / Load Balancer │
│ *.sales-agent.yourdomain.com → Sales Agent │
└──────────────────────────┬───────────────────────────────────┘
│
┌──────────────────────────┴───────────────────────────────────┐
│ Tenant Resolution Middleware │
│ Host header → subdomain → tenant context │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Tenant A │ │ Tenant B │ │ Tenant C │ │
│ │ pub-a.sales │ │ pub-b.sales │ │ pub-c.sales │ │
│ │ -agent.co │ │ -agent.co │ │ -agent.co │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└──────────────────────────┬───────────────────────────────────┘
│
┌──────────────────────────┴───────────────────────────────────┐
│ PostgreSQL (shared) │
│ All tenants share one database instance │
│ Data isolation enforced at application layer │
└──────────────────────────────────────────────────────────────┘
Set the following environment variable to enable multi-tenant mode:
ADCP_MULTI_TENANT=true
When this flag is set, the Sales Agent expects every incoming request to resolve to a specific tenant. Requests that cannot be mapped to a tenant are rejected.
Multi-tenant mode requires several domain-related environment variables:
| Variable | Required | Example | Description |
|---|---|---|---|
BASE_DOMAIN |
Yes | yourdomain.com |
The root domain of your deployment |
SALES_AGENT_DOMAIN |
Yes | sales-agent.yourdomain.com |
Base domain for tenant subdomains |
ADMIN_DOMAIN |
No | admin.yourdomain.com |
Dedicated domain for the platform admin UI |
SUPER_ADMIN_DOMAIN |
No | super.yourdomain.com |
Dedicated domain for super-admin operations |
With the configuration above, individual tenants are accessible at:
https://publisher-a.sales-agent.yourdomain.com
https://publisher-b.sales-agent.yourdomain.com
Multi-tenant mode requires a wildcard DNS record so that all tenant subdomains resolve to your Sales Agent instance.
Create a wildcard A or CNAME record:
*.sales-agent.yourdomain.com. IN A 203.0.113.10
Or, if using a CNAME:
*.sales-agent.yourdomain.com. IN CNAME your-lb.example.com.
If you are using a cloud provider’s load balancer, the CNAME approach is typically preferred since the underlying IP address may change.
You also need a wildcard TLS certificate for *.sales-agent.yourdomain.com. Options include:
The Sales Agent resolves the tenant for each incoming request using the following priority order:
| Priority | Method | Header / Source | Example |
|---|---|---|---|
| 1 | Explicit override | x-adcp-tenant request header |
x-adcp-tenant: publisher-a |
| 2 | Platform proxy header | Apx-Incoming-Host header |
Apx-Incoming-Host: publisher-a.sales-agent.yourdomain.com |
| 3 | Host header (default) | Host header |
Host: publisher-a.sales-agent.yourdomain.com |
In most deployments, the Host header is sufficient. The Apx-Incoming-Host header is useful when a CDN or reverse proxy rewrites the Host header. The x-adcp-tenant header allows programmatic tenant selection from API clients.
Session cookies are scoped to the SALES_AGENT_DOMAIN value. This means:
Domain=.sales-agent.yourdomain.compublisher-a.sales-agent.yourdomain.com does not carry over to publisher-b.sales-agent.yourdomain.comThe tenant is immediately accessible at https://<slug>.sales-agent.yourdomain.com.
Use the tenant setup script for scripted or bulk tenant creation:
python -m scripts.setup.setup_tenant \
--name "Publisher A" \
--slug "publisher-a" \
--domain "publisher-a.sales-agent.yourdomain.com"
The --slug value becomes the subdomain prefix. It must be unique, lowercase, and contain only alphanumeric characters and hyphens.
Each tenant that integrates with Google Ad Manager (GAM) needs its own service account credentials. This keeps credentials isolated and allows different publishers to connect to different GAM networks.
To configure a tenant’s GAM credentials:
The credentials are encrypted at rest using the deployment’s ENCRYPTION_KEY.
Each service account must be granted access to its respective GAM network by the publisher. Share the service account email address with the publisher and have them add it in the GAM Admin console under Global Settings > Network > API Access.
MCP clients connecting to a multi-tenant deployment must route requests to the correct tenant subdomain. The MCP server URL includes the tenant subdomain:
{
"mcpServers": {
"salesagent": {
"url": "https://publisher-a.sales-agent.yourdomain.com/mcp/",
"headers": {
"x-adcp-auth": "YOUR_MCP_TOKEN"
}
}
}
}
Each tenant has its own MCP authentication token, configured in the Admin UI under Settings > API Access.
To connect to a different tenant, change the subdomain in the URL:
{
"mcpServers": {
"salesagent": {
"url": "https://publisher-b.sales-agent.yourdomain.com/mcp/",
"headers": {
"x-adcp-auth": "PUBLISHER_B_MCP_TOKEN"
}
}
}
}
When self-signup is enabled, new publishers can register their own tenant at the /signup endpoint:
https://sales-agent.yourdomain.com/signup
Self-signup creates a new tenant with a subdomain based on the publisher’s chosen slug. The registering user becomes the tenant admin.
Self-signup is disabled by default. Enable it only if your deployment model supports open registration. Review the Security Model documentation before enabling.
Symptom: Requests return 400 Bad Request with a message about missing tenant context.
Cause: The Sales Agent could not determine which tenant the request belongs to.
Check the following:
ADCP_MULTI_TENANT=true is setcurl -v https://publisher-a.sales-agent.yourdomain.com/health
Look for the Host header in the request. If the proxy is stripping it, configure the proxy to pass Apx-Incoming-Host or x-adcp-tenant instead.
Symptom: A tenant’s custom domain does not resolve or returns a different tenant.
Check:
Symptom: Users are logged out when navigating between pages, or sessions are shared across tenants.
Verify:
SALES_AGENT_DOMAIN is set correctlyDomain attribute (check browser developer tools)Symptom: A newly created tenant returns 404.
The tenant slug must match the subdomain exactly. Verify:
# Check if the tenant exists
curl https://admin.yourdomain.com/api/tenants
Ensure the slug in the database matches the subdomain being requested.