> ## Documentation Index
> Fetch the complete documentation index at: /docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-tenant setup

> Isolate and scope AI Gateway usage per tenant with Identities or request metadata, and apply budgets, routing, and observability per tenant.

See [HTTP URLs and authentication](/docs/reference/base-urls.md) and the API reference for complete request and response formats.

When one **AI Gateway** deployment serves several customers, teams, or products, each tenant's requests must be tracked and isolated so usage, cost, and traces can be reported per tenant.

**Toqan Router** offers two request-level mechanisms for this:

* **Identities**, which represent a tenant or its end users on each request.
* **Request metadata**, which tags a call with a tenant ID or other key-value data.

This guide covers both approaches, when to use each, and a worked example that routes, budgets, and observes a single tenant end to end. For isolating the data those requests retrieve (for example knowledge bases per tenant), see [Isolating data and knowledge bases per tenant](#isolating-data-and-knowledge-bases-per-tenant).

> [!NOTE]
>
> **Managed agents**: one agent serves every tenant. The agent stays in one project, and each run attaches that tenant's [Identity](#approach-1-identity-per-tenant), which carries reporting, budgets, and trace filters. A project per tenant comes later, when a tenant's knowledge bases or datasets need their own boundary; see [Isolating data and knowledge bases per tenant](#isolating-data-and-knowledge-bases-per-tenant).

## Which approach to use

Choose the approach by how fixed each tenant is and by what needs to be enforced or reported. Use an **Identity** for tenants or end users that are known and stable, when cost and usage must be metered, capped, and reported per identity. Use **metadata** when requests only need a tag to filter on later, and the tenant isn't something to budget against.

| | **Identity per tenant** | **Request metadata** |
| - | - | - |
| Represents | A known, addressable tenant or end user | Arbitrary key-value context on a request |
| Carrier | `identity` object or `X-TOQAN-IDENTITY-ID` header | `metadata` object or `X-TOQAN-METADATA-*` headers |
| Cost & usage reporting | Grouped and filtered per identity | Not a [Reporting](/docs/ai-studio/observability/reporting-api.md) dimension; filter traces only |
| Budgets | Per-identity budget caps | Not a budget scope |
| Traces & analytics | Filtered by Identity | Filtered by `metadata.<key>` |
| Best for | Tenant billing, per-tenant budgets, end-user attribution | Tenants that don't map cleanly to a fixed identity, ad hoc tagging |

## Approach 1: Identity per tenant

An [**Identity**](/docs/ai-studio/observability/identities.md) represents a **User**, **Team**, **Project**, or **Client**. Create one identity per tenant and attach it to every request that tenant makes. Usage, cost, and traces then attribute to the identity, enabling per-tenant reporting, per-identity budgets, and trace filtering.

Create an identity for each tenant once:

```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --location 'https://router.toqan.ai/eu/v2/identities' \
--header "Authorization: Bearer $TOQAN_API_KEY" \
--header 'Content-Type: application/json' \
--data-raw '{
    "external_id": "tenant-acme",
    "display_name": "Acme Corp",
    "metadata": {
      "plan": "enterprise"
    }
}'
```

Then pass the identity on each request, in the body or as a header:

```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://router.toqan.ai/eu/v3/router/responses \
  -H "Authorization: Bearer $TOQAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-5.4-mini",
    "input": "Help me resolve a billing issue",
    "name": "SupportAssistant-Production",
    "identity": {
      "id": "tenant-acme"
    }
  }'
```

```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.TOQAN_API_KEY,
  baseURL: "https://router.toqan.ai/eu/v3/router",
});

const response = await client.responses.create({
  model: "openai/gpt-5.4-mini",
  input: "Help me resolve a billing issue",
  name: "SupportAssistant-Production",
  identity: { id: "tenant-acme" },
});

console.log(response.output_text);
```

```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from openai import OpenAI
import os

client = OpenAI(
    api_key=os.environ.get("TOQAN_API_KEY"),
    base_url="https://router.toqan.ai/eu/v3/router",
)

response = client.responses.create(
    model="openai/gpt-5.4-mini",
    input="Help me resolve a billing issue",
    extra_body={
        "name": "SupportAssistant-Production",
        "identity": {"id": "tenant-acme"},
    },
)

print(response.output_text)
```

To track both a tenant and the person acting for it, give each end user an identity ID such as `tenant-{orgId}-{userId}`, or create one identity per tenant and share it across that tenant's users. See [Attaching an identity to a request](/docs/ai-studio/observability/identities.md#attaching-an-identity-to-a-request) for the body, header, and API-key-owner sources, in the order the gateway checks them.

## Approach 2: Request metadata

For tenants that don't map to a fixed set of identities, tag requests with [**request metadata**](/docs/ai-gateway/request-metadata.md). Metadata adds key-value context such as `tenant_id`, `region`, or `tier`, which appears on traces as `metadata.<key>` span attributes and can be used as a trace filter.

Pass a `metadata` object on the request:

```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://router.toqan.ai/eu/v3/router/responses \
  -H "Authorization: Bearer $TOQAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-5.4-mini",
    "input": "Summarize my contracts",
    "metadata": {
      "tenant_id": "acme",
      "region": "eu-west-1"
    }
  }'
```

```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.TOQAN_API_KEY,
  baseURL: "https://router.toqan.ai/eu/v3/router",
});

const response = await client.responses.create({
  model: "openai/gpt-5.4-mini",
  input: "Summarize my contracts",
  metadata: { tenant_id: "acme", region: "eu-west-1" },
});

console.log(response.output_text);
```

```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from openai import OpenAI
import os

client = OpenAI(
    api_key=os.environ.get("TOQAN_API_KEY"),
    base_url="https://router.toqan.ai/eu/v3/router",
)

response = client.responses.create(
    model="openai/gpt-5.4-mini",
    input="Summarize my contracts",
    extra_body={"metadata": {"tenant_id": "acme", "region": "eu-west-1"}},
)

print(response.output_text)
```

When the request body cannot be modified, set the metadata with headers instead. Each `X-TOQAN-METADATA-<key>` header sets one metadata key, lowercased from the header suffix:

```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://router.toqan.ai/eu/v3/router/responses \
  -H "Authorization: Bearer $TOQAN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-TOQAN-METADATA-TENANT_ID: acme" \
  -H "X-TOQAN-METADATA-REGION: eu-west-1" \
  -d '{
    "model": "openai/gpt-5.4-mini",
    "input": "Summarize my contracts"
  }'
```

Metadata feeds **routing rules**, **guardrail rules**, and **budget matching**, and appears as a trace filter, so requests can be routed, guarded, and inspected per tenant without an identity. Metadata is **not** a [Reporting](/docs/ai-studio/observability/reporting-api.md) dimension or a budget scope. To aggregate cost per tenant or cap a tenant's spend, use the identity or project scope instead.

> [!NOTE]
>
> Keep metadata to a small, fixed set of low-cardinality keys such as `tenant_id`, `region`, and `tier`. High-cardinality values, such as per-request IDs, defeat filtering and increase storage. See [Request metadata best practices](/docs/ai-gateway/request-metadata.md#best-practices).

## Identity and metadata together

Use an **Identity** to meter and budget a tenant; use **metadata** for extra context such as region or product line. The two work together: attach the tenant's **Identity** for per-tenant attribution and budgets, then add **metadata** to describe the request.

## Worked example: one tenant, end to end

This example sets up **Acme Corp** as a tenant with per-tenant routing, a monthly budget, and per-tenant observability, using an identity plus a routing rule keyed on identity and metadata.

**1. Create the tenant identity**

```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl --location 'https://router.toqan.ai/eu/v2/identities' \
--header "Authorization: Bearer $TOQAN_API_KEY" \
--header 'Content-Type: application/json' \
--data-raw '{
    "external_id": "tenant-acme",
    "display_name": "Acme Corp"
}'
```

**2. Cap Acme's spend**

Navigate to **Settings > Organization > Budgets** and click **Create**. Set **Scope > Applies to** to **Identity**, select the `tenant-acme` identity, and set a monthly **Cost** limit. When the limit is reached, requests matching Acme's identity are rejected until the monthly reset, while other tenants keep working. See [Budgets](/docs/ai-gateway/budgets.md) for period limits and alerts.

> [!WARNING]
>
> Anyone with a key can set an **Identity** on a request; the gateway does not check that the key owns that identity. So identity-based budgets and cost reporting are only as trustworthy as the keys that can reach the gateway. Give keys only to trusted backends. To cap spend for untrusted tenants, use a per-tenant key or per-tenant project instead of identity matching.

**3. Route and observe Acme's traffic**

On each request, attach the identity and a metadata key so routing and trace filters can target Acme:

```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://router.toqan.ai/eu/v3/router/responses \
  -H "Authorization: Bearer $TOQAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "agent/support-bot",
    "input": "Reset my password",
    "identity": {
      "id": "tenant-acme"
    },
    "metadata": {
      "tenant_id": "acme"
    },
    "memory": {
      "entity_id": "tenant-acme"
    },
    "tools": [
      {
        "type": "mcp",
        "server_url": "https://mcp.acme.example/mcp",
        "headers": { "Authorization": "Bearer {{acme_token}}" }
      }
    ],
    "variables": {
      "acme_token": { "secret": true, "value": "acme-mcp-token" }
    }
  }'
```

```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.TOQAN_API_KEY,
  baseURL: "https://router.toqan.ai/eu/v3/router",
});

const response = await client.responses.create({
  model: "agent/support-bot",
  input: "Reset my password",
  identity: { id: "tenant-acme" },
  metadata: { tenant_id: "acme" },
  memory: { entity_id: "tenant-acme" },
  tools: [
    {
      type: "mcp",
      server_url: "https://mcp.acme.example/mcp",
      headers: { Authorization: "Bearer {{acme_token}}" },
    },
  ],
  variables: {
    acme_token: { secret: true, value: "acme-mcp-token" },
  },
});
```

```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
from openai import OpenAI
import os

client = OpenAI(
    api_key=os.environ.get("TOQAN_API_KEY"),
    base_url="https://router.toqan.ai/eu/v3/router",
)

response = client.responses.create(
    model="agent/support-bot",
    input="Reset my password",
    extra_body={
        "identity": {"id": "tenant-acme"},
        "metadata": {"tenant_id": "acme"},
        "memory": {"entity_id": "tenant-acme"},
        "tools": [
            {
                "type": "mcp",
                "server_url": "https://mcp.acme.example/mcp",
                "headers": {"Authorization": "Bearer {{acme_token}}"},
            }
        ],
        "variables": {"acme_token": {"secret": True, "value": "acme-mcp-token"}},
    },
)
```

The memory store is scoped to `tenant-acme`, so each tenant keeps its own conversation memory. The MCP token arrives as a `secret` variable and stays out of traces and logs; see Per-request credentials for the placeholder and secret rules.

Create a [Routing Rule](/docs/ai-gateway/configuration/routing-rules.md) whose condition matches the **Identity** `tenant-acme` to send Acme's traffic to a dedicated model or variant. Combine conditions on identity and metadata (for example region) with the rule builder.

**4. Report Acme's usage and cost**

Query the [Reporting API](/docs/ai-studio/observability/reporting-api.md), filtering to the tenant identity:

```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
FROM=$(date -u -v-1d +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u -d "1 day ago" +%Y-%m-%dT%H:%M:%SZ)
TO=$(date -u +%Y-%m-%dT%H:%M:%SZ)
curl -X POST "https://router.toqan.ai/eu/v2/reporting" \
  -H "Authorization: Bearer $TOQAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"metric\": \"genai.usage\",\"from\": \"$FROM\",\"to\": \"$TO\",\"grain\": \"day\",\"group_by\": [\"identity\"],\"filters\": [{\"field\": \"identity\",\"op\": \"eq\",\"values\": [\"tenant-acme\"]}]}"
```

In the **Traces** view, filter by **Identity** `tenant-acme`, or by **Metadata** `tenant_id: acme`, to inspect Acme's latency, token usage, and errors.

> [!NOTE]
>
> Scheduled agent runs carry no `identity`, so their cost and usage do not attribute to a tenant. When scheduled runs need per-tenant Reporting, run them from a customer-side scheduler that calls the Responses API with `identity` attached, or read their cost from the **Traces** view filtered by metadata.

## Isolating data and knowledge bases per tenant

Request-level attribution meters usage, but it does not separate the data a tenant can retrieve. To isolate content, put each tenant in its own **Project**.

* **Projects are the isolation boundary.** A **Project** holds Deployments, Prompts, Agents, Knowledge Bases, and Datasets, with project-scoped observability and budgets.
* **Knowledge bases follow the project.** A knowledge base lives under a `project/path` and is retrieved through a Deployment in that project. With one project per tenant, a tenant's key cannot reach another tenant's knowledge base.
* **Single-project keys stay inside the tenant.** Only keys created with **single-project** scope are confined to that tenant's project. Workspace-wide and legacy keys can reach every project, so issue single-project keys to tenants and never hand out a workspace-wide key.
* **Or share one knowledge base among trusted tenants.** If tenants trust one another, tag chunks with a `client_id` metadata field and add a `filter_by` condition to narrow retrieval to that tenant. This is a query-time filter, not enforced access control: any caller can omit it and read every tenant's chunks. Use it only among trusted tenants; otherwise separate tenants into per-tenant projects. See Chunk Metadata in Knowledge Bases.

For a full setup: one **project per tenant** for data isolation and per-tenant budgets, an **Identity** (or the project-scoped key) so usage reports and traces attribute to the tenant, and **metadata** for extra context such as region or product line.

## See also

> [card] [Identities](/docs/ai-studio/observability/identities.md)
>
> Create identities and attach them to requests for per-tenant attribution and budgets.

> [card] [Request Metadata](/docs/ai-gateway/request-metadata.md)
>
> Attach name, identity, thread, and custom metadata to AI Gateway requests.

> [card] [Budgets](/docs/ai-gateway/budgets.md)
>
> Cap spend per workspace, project, identity, API key, provider, or model.

> [card] [Reporting API](/docs/ai-studio/observability/reporting-api.md)
>
> Query per-tenant usage, cost, and performance programmatically.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.