Gateway API
The governed inference gateway: the /v1/messages, /v1/chat/completions and /v1/responses contracts, the required x-session-id header, the model list, status codes, and the three profile API URLs.
On this page
TL;DR: The gateway serves three inference endpoints under /v1, one for
each client wire format. Each one authenticates, authorises, governs and
audits the call before it is forwarded upstream. Every request needs a
credential (a bridge JWT or a personal access token) and an
x-session-id header that matches a session the gateway issued. The model
must be one the catalog or a route names, or the request is refused before
any upstream call. Clients normally never see any of this: the bridge
configures Claude Code and OpenCode for you.
Endpoints
| Method and path | Format | Used by |
|---|---|---|
POST /v1/messages |
Anthropic Messages (model, max_tokens, messages[]) |
Claude Code, the Anthropic SDKs |
POST /v1/chat/completions |
OpenAI Chat Completions | OpenCode and other OpenAI-compatible tools |
POST /v1/responses |
OpenAI Responses | Responses-API clients |
GET /v1/models |
model list | model pickers; lists every advertised model |
All three inference endpoints share one pipeline: authentication, session
check, route and entitlement, governance chain, safety scanners, quota
windows, dispatch, audit. The format a client speaks is independent of the
provider that serves it. An OpenCode request on /v1/chat/completions for
claude-sonnet-5-5 is served by Claude on Vertex, exactly like the same model
requested on /v1/messages.
Base URL for OpenAI SDK clients
An OpenAI-compatible client takes a base URL and appends /chat/completions
(or /responses, /models) itself. Set it to:
https://<host>/v1
There is no /openai/v1 prefix. A client configured with
https://<host>/openai/v1 requests /openai/v1/chat/completions, which does
not exist, and gets 404. Anthropic SDK clients take the bare host
(https://<host>) and append /v1/messages.
Request contract
| Header | Purpose | If missing or wrong |
|---|---|---|
Authorization: Bearer <jwt or PAT> or x-api-key: <PAT> |
Caller identity | 401 |
x-session-id: <session id> |
Binds the call to a session for audit and conversation continuity | missing: 400 (missing required x-session-id header); not the caller's session: 401 |
x-gateway-conversation-id: <id> (optional) |
Pins the conversation; otherwise it is derived from the messages | — |
x-systemprompt-scope-<dimension>: <value> (optional) |
Attributes the request to one value in a scope dimension, such as project |
see Scope headers |
The session must be one the gateway issued:
- Bridge JWT. The session is minted with the token.
x-session-idmust equal the JWT'ssession_idclaim. - Personal access token. First mint a session with
POST /api/public/gateway/sessions, using the same PAT, then send the returnedsession_id. An unknown or revoked session gets401withunknown or revoked session; mint one at POST /api/public/gateway/sessions.
A CLI admin session token is not a gateway credential. It fails with
kid does not match any known signing key.
Scope headers
Every request is attributed to one value in each scope dimension the instance registers. For each dimension the value is resolved in this order:
- the
x-systemprompt-scope-<dimension>header; - the value the API key is bound to for that dimension;
- the caller's default (first) value for that dimension.
The chargeback dimensions this instance registers:
| Header | Value | Default when absent |
|---|---|---|
x-systemprompt-scope-project: <project id> |
a project the caller is a live member of | the caller's primary project |
x-systemprompt-scope-cost_centre: <code> |
the cost centre of one of the caller's projects | the primary project's cost centre |
x-systemprompt-scope-practice: <name> |
the practice of one of the caller's projects | the primary project's practice |
The dimension name is written as registered, underscore included
(cost_centre). The instance also registers access dimensions (group,
role, organization and others) that a header can name under the same
rules; they exist for access rules and quota windows, not chargeback.
| Answer | Cause |
|---|---|
403 |
the header or key-bound value is not one the caller holds (not a member of <dimension> '<value>'). An unknown value answers the same, so the header cannot probe for existence |
400 |
a header names a dimension the instance does not register, is empty, or appears more than once |
400 scope_required |
a dimension listed in gateway.require_scopes resolves to nothing. This instance requires none |
The resolved values and their source are recorded on the request (see Audit Trail) and can pick a route's per-scope chain (see Model Gateway).
Models
The AI catalog ships in the image as five files under services/ai/:
credentials.yaml (who authenticates and who pays), providers.yaml
(vendor accounts and their endpoints, one wire and one URL each),
models.yaml (each model once, with its limits, price and the clients:
that list it), deployments.yaml (which endpoints serve each model, in
failover order) and gateway.yaml (the one default_model and the policy
routes). A request is dispatched only if its model is a declared model or
a route alias; nothing else is forwarded.
| Family | Ids a client can request | Served by |
|---|---|---|
| Claude | claude-opus-5-5, claude-sonnet-5-5, claude-haiku-5-5 |
Anthropic's API, platform key |
| DeepSeek | deepseek-v4-pro, deepseek-flash |
DeepSeek's API, platform key (deepseek-v4-pro fails over to OpenCode Go) |
| OpenCode Go | go-* (and go-deepseek-v4-pro, an alias of deepseek-v4-pro pinned to OpenCode Go) |
OpenCode Go, the member's own key; see OpenCode Go |
Claude is served only by the customer's Anthropic key. Gemini and Vertex AI are not served.
GET /v1/models is the authoritative list for a running instance. Retired
ids are not declared and are refused.
gateway.default_model (claude-sonnet-5-5) is the default the bridge
hands to Claude Code. A request is not rerouted to it. Claude Code's [1m]
1M-context marker is never written in config: the bridge derives it from the
model's limits.context_window, and the gateway strips it before routing.
Status codes before the upstream call
| Status | Cause |
|---|---|
400 |
missing x-session-id, a malformed body, or a model not permitted by gateway policy (model '<id>' is not permitted by gateway policy) |
401 |
missing, invalid, expired or revoked credential, or a session that does not belong to the caller |
403 |
the caller is not entitled to the matched route (authz denied [<policy>]: <reason>), a scope value the caller does not hold, or a model outside the API key's allowlist (model '<id>' is not allowed for this API key) |
429 |
a quota window is exhausted. Not returned on this instance, where quotas run in warn mode |
503 |
the process is at its server.max_in_flight ceiling (error_key: "overloaded", Retry-After: 1). Unset on this instance, so never returned |
A quota 429 carries a Retry-After header equal to the seconds until the
window resets, and a machine-readable body:
{
"type": "error",
"error": {
"type": "rate_limit_error",
"message": "...",
"quota": {
"window_seconds": 86400,
"subject": "user",
"dimension": "input_tokens",
"limit": 5000000,
"used": 5000120,
"resets_at": "2026-10-08T00:00:00Z",
"retry_after_seconds": 3600
}
}
}
The values above are illustrative. subject is the window's subject (user,
api_key or a scope dimension) and dimension names the ceiling breached:
requests, input_tokens, output_tokens or cost_microdollars.
Governance and safety denials also answer before the upstream call. Both planes run in warn mode on this instance and do not deny: see Governance Modes.
Governance requirements on routes
Providers and models carry a governance: block (european, no_retain),
and a route can require either with requires:. A route whose target does
not satisfy its requirement fails boot, and dispatch checks the resolved
target again. No route on this instance declares a requirement today. The
walkthrough is in
Model Gateway, Routing & Data Residency.
Profile API URLs
A profile declares three server URLs; pick the right one for the caller:
| Field | Use it for |
|---|---|
api_internal_url |
Service-to-service calls inside the deployment network |
api_server_url |
Server API base URL; the listening interface and port are configured separately through host and port |
api_external_url |
The public, host-facing address; used for OAuth/WebAuthn URL generation and consumed by session login |
When the host-published port differs from the in-container port (as in the air-gap
stack), an in-container caller must use the in-container address and host callers must
use the published one — a single value cannot satisfy both. The air-gap profile pins
all three to the in-container :8080 and automated host callers pass the published
port explicitly.