> This page is for Gateway.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.merge.dev/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.merge.dev/_mcp/server.

# Embedded Routing Stack

> Provision each of your end customers with their own routing policies, vendor keys, budgets, guardrails, and usage through the /v1/customers API, and scope requests to a customer at request time.

Embedded Routing gives each of your end customers its own routing, vendor keys, and spend limits under your one organization. Use it when you resell Gateway to tenants without dashboard accounts. The `/v1/customers` endpoints take your organization-level key (`Bearer mg_...`), derive the organization from it, and use UUIDs for every ID. See the [Embedded Routing API reference](/merge-gateway/embedded-routing) for schemas.

## How the pieces fit

A request scoped to a **Customer** uses everything below it.

| Object                                                                      | Role at request time                                                                                                       |
| --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| [Customer](/merge-gateway/embedded-routing-stack/customers)                 | The tenant. Its `status` and key usage mode apply to every request.                                                        |
| [Routing policy](/merge-gateway/embedded-routing-stack/routing-policies)    | Picks the model when the request doesn't pin one                                                                           |
| [Customer key](/merge-gateway/embedded-routing-stack/customer-keys)         | The customer's own vendor credential. The key usage mode decides whether it, your key, or Merge-managed credentials serve. |
| [Customer API key](/merge-gateway/embedded-routing-stack/customer-api-keys) | A Gateway key you give the customer, which scopes its requests automatically                                               |
| [Budget](/merge-gateway/embedded-routing-stack/budgets)                     | Caps the customer's spend                                                                                                  |
| [Guardrails](/merge-gateway/embedded-routing-stack/guardrails)              | Prompt injection and DLP overrides on your organization's policy                                                           |
| [Usage](/merge-gateway/embedded-routing-stack/usage)                        | The customer's spend, split by whose credential served it                                                                  |

`PUT /v1/customers/{customer_id}/controls` sets per-customer vendor, region, and ZDR controls, which override organization settings and work like [project-scoped controls](/merge-gateway/security/zero-data-retention#scoped-controls): send only the fields you override, and `DELETE` to inherit again.

## Sending requests

Set `customer` to the Customer's `id`, send your own ID in the [`X-Merge-Customer-Origin-Id`](/merge-gateway/embedded-routing-stack/customers#address-customers-by-your-own-id) header (which creates the customer on first use), or use the customer's [API key](/merge-gateway/embedded-routing-stack/customer-api-keys). Routing then depends on `model` and `routing_policy_id`:

| Request                                        | Routes via                                                      |
| ---------------------------------------------- | --------------------------------------------------------------- |
| `customer`, no `model`                         | The customer's default policy, else your organization's default |
| `customer` and `routing_policy_id`, no `model` | That policy                                                     |
| `customer` and `model`                         | That model directly. Keys, budget, and usage still apply.       |

**`cURL`**

```bash title="cURL"
curl https://api-gateway.merge.dev/v1/responses \
  -H "Authorization: Bearer $MERGE_GATEWAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "customer": "9f8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "routing_policy_id": "2a1b3c4d-5e6f-4708-9a0b-1c2d3e4f5a6b",
    "input": [
      {"type": "message", "role": "user", "content": "Summarize this ticket in one sentence."}
    ]
  }'
```

**`OpenAI SDK`**

```python title="OpenAI SDK"
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["MERGE_GATEWAY_API_KEY"],
    base_url="https://api-gateway.merge.dev/v1/openai",
)

completion = client.chat.completions.create(
    model="default_routing",  # let the customer's policy choose
    messages=[{"role": "user", "content": "Summarize this ticket in one sentence."}],
    extra_body={
        "customer": "9f8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
        "routing_policy_id": "2a1b3c4d-5e6f-4708-9a0b-1c2d3e4f5a6b",
    },
)
print(completion.choices[0].message.content)
```

The OpenAI, Anthropic, LangChain, and AI SDK surfaces accept both fields in the body; since those SDKs require a model, send `default_routing` to let the policy route. Any endpoint also accepts `X-Merge-Customer` and `X-Merge-Routing-Policy-Id` headers, which can't override a customer API key's scope. `X-Merge-Customer` is unrelated to the tracing tag `X-Customer-ID`.

Combining `customer` with a project scope (a project API key, `X-Project-Id`, or `project_id`) returns `400` `customer_project_conflict`. Non-streaming responses name the serving policy in `x-merge-routing-policy-id`.

## Listing a customer's allowed models

`GET /v1/customers/{customer_id}/models` returns what the customer can route to after its controls, key usage mode, and your organization's vendor access apply, for use in a model picker.

```json
{
  "scope": "customer",
  "scope_id": "9f8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
  "restrictions_active": true,
  "key_usage_mode": "byok_only",
  "models": [
    { "model": "openai/gpt-5.6-sol", "provider": "openai", "vendors": ["openai"] },
    { "model": "mistral/mistral-large-2512", "provider": "mistral", "vendors": ["mistral"] }
  ],
  "gated_model_ids": []
}
```

`vendors` lists the hosts that can serve each model; `gated_model_ids` are allowed by the customer's controls but not your organization's vendor access. `key_usage_mode` is lowercase here, uppercase on the Customer. `GET /v1/models` with a customer API key or `X-Merge-Customer` returns the same list.

## Status codes

| Status | type or code                                    | When                                                                                |
| ------ | ----------------------------------------------- | ----------------------------------------------------------------------------------- |
| `400`  | `customer_project_conflict`                     | The request combines `customer` with a project scope                                |
| `400`  | `customer_mismatch`, `routing_policy_mismatch`  | A header conflicts with the body or with a customer API key                         |
| `400`  | `invalid_customer`, `invalid_routing_policy_id` | A header value isn't a UUID                                                         |
| `400`  | `invalid_customer_origin_id`                    | `X-Merge-Customer-Origin-Id` is empty or longer than 255 characters                 |
| `401`  | `authentication_error`                          | Missing or invalid Gateway key                                                      |
| `402`  | `budget_exceeded`                               | The customer's `HARD` budget is exhausted                                           |
| `403`  | `permission_error`                              | The customer is inactive or deleted                                                 |
| `403`  | `customer_scoped_key_forbidden`                 | A customer API key called an organization-level endpoint                            |
| `403`  | `customer_limit_reached`                        | `X-Merge-Customer-Origin-Id` would create a customer past your organization's limit |
| `403`  | `customer_deleted`                              | Minting a key by `origin_id` for a customer that was deleted                        |
| `404`  | `customer_not_found`                            | No customer in your organization has that UUID                                      |
| `404`  | `provider_credentials_missing`                  | `BYOK_ONLY` or `CUSTOMER_ONLY` left no credential for the vendor                    |
| `409`  |                                                 | Duplicate `origin_id`, a second budget, or a sixth customer API key                 |
| `422`  |                                                 | Validation, such as a `customer` body field that isn't a UUID                       |
| `503`  | `internal_error`                                | The customer's configuration couldn't be read. Retry.                               |

Streaming requests get these as HTTP statuses when the failure precedes the first token, and as an in-band `response.error` frame ([Streaming](/merge-gateway/streaming)) after.

## Next steps

#### [Customers](/merge-gateway/embedded-routing-stack/customers)

Provision a customer and manage its lifecycle

#### [Customer API keys](/merge-gateway/embedded-routing-stack/customer-api-keys)

Hand each customer its own scoped Gateway key

#### [Embedded Routing API reference](/merge-gateway/embedded-routing)

Full request and response schemas