Embedded Routing Stack
For platforms reselling Gateway: provision routing, keys, budgets, and usage for each of your end customers
The Embedded Routing Stack lets a platform built on Gateway give each of its own end customers their own routing policies, BYOK provider keys, budget, and usage, provisioned with your production key. Use it when you resell Gateway to your customers and need per-customer control without a dashboard seat for each one. A Customer is the object you manage under /v1/customers; you attach it to an LLM request with the customer field, and everything is scoped to your organization automatically because the organization is derived from your key.
All requests go to https://api-gateway.merge.dev with Authorization: Bearer mg_<your_production_key>, plus Content-Type: application/json on any request that has a JSON body. IDs are Merge UUIDs, timestamps are created_at and modified_at, and there is no org_id field.
Data models
Each object has its own reference page.
Sending requests
Send to /v1/responses as usual, plus a customer field set to the Customer’s id (the Merge UUID returned on create, not your own origin_id). Sending anything that isn’t a UUID is rejected with 422, and a well-formed UUID that matches no Customer is rejected with 404 (see Status codes). The Customer’s provider key, budget, and usage attribution then apply automatically.
To let a policy route, omit model. If you pin a model, that model is used directly and the policy (including the default) is bypassed. Budget, keys, and usage attribution still apply, only routing is skipped.
Non-streaming responses echo which policy served the request in the x-merge-routing-policy-id response header. Add -i to your curl (or -D -) to include the response headers and see it.
Checking usage
Fetch a spend report for a date range. Pass start and end as YYYY-MM-DD. The response carries the Usage fields: the customer_byok_spend / organization_byok_spend / merge_spend split (their sum is the customer’s total) plus per-model, per-provider, and per-routing-policy breakdowns.
Status codes
Request errors carry a machine-readable type (and, where useful, a code) so you can branch on them.
Fail-closed by design: BYOK_ONLY never falls back to managed credentials, and a deactivated or deleted customer’s requests are rejected rather than silently served.
Errors on streaming requests
A streaming request ("stream": true) returns HTTP 200 the moment the stream opens, before the model runs. A rejection caught before then (inactive customer, exhausted budget, unknown customer) still returns the real HTTP status from the table above. A failure that surfaces after the stream has opened (no usable provider key, no route, or a provider fault mid-response) can no longer change the status code, so it arrives in-band as a terminal error frame:
Treat any frame whose object is response.error (or that carries a top-level error) as a failed stream and stop reading. A successful stream instead ends with a frame whose object is response.done. The error payload carries the same type, code, and status_code you would have received non-streamed, so you can branch on them the same way.