> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orq.ai/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.

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.

**Orq.ai** 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).

## 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-ORQ-IDENTITY-ID` header          | `metadata` object or `X-ORQ-METADATA-*` headers                                              |
| Cost & usage reporting | Grouped and filtered per identity                        | Not a [Reporting](/docs/ai-studio/observability/reporting-api) 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) 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:

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

  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import { Orq } from "@orq-ai/node";

  const orq = new Orq({ apiKey: process.env.ORQ_API_KEY ?? "" });

  const identity = await orq.identities.create({
    externalId: "tenant-acme",
    displayName: "Acme Corp",
    metadata: { plan: "enterprise" },
  });
  ```

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

  orq = Orq(api_key=os.getenv("ORQ_API_KEY"))

  identity = orq.identities.create(
      external_id="tenant-acme",
      display_name="Acme Corp",
      metadata={"plan": "enterprise"},
  )
  ```
</CodeGroup>

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

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST https://my.orq.ai/v3/router/responses \
    -H "Authorization: Bearer $ORQ_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.ORQ_API_KEY,
    baseURL: "https://my.orq.ai/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("ORQ_API_KEY"),
      base_url="https://my.orq.ai/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)
  ```
</CodeGroup>

Identity management uses the **Orq** SDK (`orq.identities.create`); requests run through the OpenAI-compatible SDK (`openai` client) or the `/v3/router` endpoints, which accept the `identity` and `metadata` fields on the request body.

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#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). 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:

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST https://my.orq.ai/v3/router/responses \
    -H "Authorization: Bearer $ORQ_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.ORQ_API_KEY,
    baseURL: "https://my.orq.ai/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("ORQ_API_KEY"),
      base_url="https://my.orq.ai/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)
  ```
</CodeGroup>

When the request body cannot be modified, set the metadata with headers instead. Each `X-ORQ-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://my.orq.ai/v3/router/responses \
  -H "Authorization: Bearer $ORQ_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-ORQ-METADATA-TENANT_ID: acme" \
  -H "X-ORQ-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) 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#best-practices).
</Note>

## 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.

<Steps>
  <Step title="Create the tenant identity">
    <CodeGroup>
      ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
      curl --location 'https://my.orq.ai/v2/identities' \
      --header "Authorization: Bearer $ORQ_API_KEY" \
      --header 'Content-Type: application/json' \
      --data-raw '{
          "external_id": "tenant-acme",
          "display_name": "Acme Corp"
      }'
      ```

      ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
      import { Orq } from "@orq-ai/node";

      const orq = new Orq({ apiKey: process.env.ORQ_API_KEY ?? "" });

      const identity = await orq.identities.create({
        externalId: "tenant-acme",
        displayName: "Acme Corp",
      });
      ```

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

      orq = Orq(api_key=os.getenv("ORQ_API_KEY"))

      identity = orq.identities.create(
          external_id="tenant-acme",
          display_name="Acme Corp",
      )
      ```
    </CodeGroup>
  </Step>

  <Step title="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) 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.
    </Warning>
  </Step>

  <Step title="Route and observe Acme's traffic">
    On each request, attach the identity and a metadata key so routing and trace filters can target Acme:

    <CodeGroup>
      ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
      curl -X POST https://my.orq.ai/v3/router/responses \
        -H "Authorization: Bearer $ORQ_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "model": "agent/support-bot",
          "input": "Reset my password",
          "identity": {
            "id": "tenant-acme"
          },
          "metadata": {
            "tenant_id": "acme"
          }
        }'
      ```

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

      const client = new OpenAI({
        apiKey: process.env.ORQ_API_KEY,
        baseURL: "https://my.orq.ai/v3/router",
      });

      const response = await client.responses.create({
        model: "agent/support-bot",
        input: "Reset my password",
        identity: { id: "tenant-acme" },
        metadata: { tenant_id: "acme" },
      });
      ```

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

      client = OpenAI(
          api_key=os.environ.get("ORQ_API_KEY"),
          base_url="https://my.orq.ai/v3/router",
      )

      response = client.responses.create(
          model="agent/support-bot",
          input="Reset my password",
          extra_body={
              "identity": {"id": "tenant-acme"},
              "metadata": {"tenant_id": "acme"},
          },
      )
      ```
    </CodeGroup>

    Create a [Routing Rule](/docs/ai-gateway/configuration/routing-rules) 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.
  </Step>

  <Step title="Report Acme's usage and cost">
    Query the [Reporting API](/docs/ai-studio/observability/reporting-api), filtering to the tenant identity:

    <CodeGroup>
      ```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://my.orq.ai/v2/reporting" \
        -H "Authorization: Bearer $ORQ_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\"]}]}"
      ```

      ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
      from orq_ai_sdk import Orq
      from datetime import datetime, timedelta, timezone
      import os

      orq = Orq(api_key=os.getenv("ORQ_API_KEY"))

      result = orq.reporting.query(
          metric="genai.usage",
          from_=datetime.now(timezone.utc) - timedelta(days=1),
          to=datetime.now(timezone.utc),
          grain="day",
          group_by=["identity"],
          filters=[{"field": "identity", "op": "eq", "values": ["tenant-acme"]}],
      )
      ```
    </CodeGroup>

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

## 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**](/docs/ai-studio/get-started/projects):

* **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](/docs/ai-studio/ai-engineering/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

<CardGroup cols={2}>
  <Card title="Identities" icon="user" href="/docs/ai-studio/observability/identities">
    Create identities and attach them to requests for per-tenant attribution and budgets.
  </Card>

  <Card title="Request Metadata" icon="tag" href="/docs/ai-gateway/request-metadata">
    Attach name, identity, thread, tags, and custom metadata to AI Gateway requests.
  </Card>

  <Card title="Budgets" icon="coins" href="/docs/ai-gateway/budgets">
    Cap spend per workspace, project, identity, API key, provider, or model.
  </Card>

  <Card title="Reporting API" icon="chart-line" href="/docs/ai-studio/observability/reporting-api">
    Query per-tenant usage, cost, and performance programmatically.
  </Card>
</CardGroup>
