Product Surfaces

The exact routes an issued key reaches, the wire shapes they speak, the model vocabulary they accept, and the fields they refuse.

Product Surfaces

The AI Gateway's product boundary is an allowlist of routes. A DevZero issued key reaches exactly these, and the published contract mirrors the allowlist one-to-one -- there is no editorial trimming and nothing outside it is part of the product.

Inference routes

RouteWire shapeNotes
POST /v1/chat/completionsOpenAI Chat CompletionsThe OpenAI-compatible surface. Gemini-shaped callers use this route too.
POST /v1/responsesOpenAI Responses
POST /v1/messagesNative Anthropic MessagesOn the Anthropic listener. Anthropic prompt caching (cache_control) passes through unchanged while the compression pipeline is off; with it enabled the lossless cache_align transform may add or relocate the breakpoint to the end of the stable prefix. Either way a Claude Code harness reaches the product by base-URL swap.

Stored-response state operations

RoutePurpose
GET /v1/responses/{response_id}Retrieve a stored response.
DELETE /v1/responses/{response_id}Delete a stored response.
GET /v1/responses/{response_id}/input_itemsList a stored response's input items.
DELETE /v1/responses/{response_id}/input_itemsForwarded verbatim; the upstream's own answer, including "not supported", is returned unmodified.

State operations are not inference: no model, no routing decision, no governance gate and no caching. A stored response is mutable server-side state, and these routes go to the account whose credential created it.

Why hostnames, not paths

The OpenAI-compatible surface and the OpenAI native surface publish the same method and paths to different upstreams. Nothing downstream can separate them by path, so hostname or port is the only discriminator -- which is why the surfaces cannot be collapsed onto one listener.

A hosted install is reached through one gateway on :443, multiplexed by hostname: a base host plus anthropic-, gemini- and openai- prefixed variants of it. A self-hosted install can opt into the same shape with the chart's ingress values, which derive the three native hostnames by prefixing the base host exactly as the hosted provisioner does. It is off by default; without it, clients address the listener ports directly.

What is deliberately outside the contract

  • Passthrough listener traffic -- anything off the routes above.
  • WebSocket relays.
  • POST /v1/messages/count_tokens.

These keep their existing behaviour. They are not broken and not deprecated; they are simply not the product surface, and the published contract makes no promise about them.

Model vocabulary

The model field uses the gateway's catalog vocabulary, author/slug:

anthropic/claude-sonnet-4-5
openai/gpt-4o
InputOutcome
A well-formed author/slug id the upstream knowsServed.
A native vendor spelling, e.g. gpt-4oRejected with invalid_model_id.
A well-formed id the serving upstream does not knowRejected with model_not_found.
Variant suffixes, e.g. :freeNot supported.
No model at allThe gateway resolves your team's configured default. If no rung of the profile ladder configures one, the request fails with no_default_model -- never a platform default.

The contract requires model, and new integrations should always send it. The default-resolution behaviour exists so integrations that predate the contract keep working, not as a recommended pattern. An explicitly named model is never substituted.

The pattern for a model id is ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$, maximum 256 characters.

Refused body fields

The upstream marketplace's own routing knobs are refused at the top level of every inference body, with 400 unsupported_routing_field:

provider   models   route   transforms   preset   plugins

Express routing intent through dz_router instead, or configure it server-side as a routing profile.

Everything else in the body is vendor-owned and passes through unpoliced. Use your vendor SDK's own types: the contract is deliberately loose there so a new vendor field is never blocked by DevZero's schema.

Calling a surface

Point the official openai SDK at your gateway and authenticate with an issued key. Gateway controls ride X-Dz-* request headers, so the SDK's own default_headers parameter drives all of them.

from openai import OpenAI

client = OpenAI(
    base_url="https://<your-gateway-host>/v1",
    api_key="sk-dz-...",
    default_headers={"X-Dz-Project": "checkout"},
)

resp = client.chat.completions.create(
    model="anthropic/claude-sonnet-4-5",
    messages=[{"role": "user", "content": "Hello"}],
)

<your-gateway-host> is a placeholder. There is deliberately no default gateway hostname in the published contract -- nothing should silently point at an endpoint that is not yours. Your gateway's own URL is shown on its source detail in the dashboard, and the install flow hands it to you.

Errors

Refusals on the keyed surface use the OpenAI error envelope, and error.code names the outcome:

CodeCause
invalid_model_idThe model value is not in author/slug form.
model_not_foundA well-formed id the serving upstream does not know.
no_default_modelNo model was sent and no rung of the profile ladder configures one.
invalid_dz_routerA malformed override, a repeated X-Dz-Router header, or an unknown field inside the override.
dz_router_conflictThe override was presented on both carriers.
dz_router_too_largeThe X-Dz-Router header exceeded 8192 bytes.
unsupported_routing_fieldThe body carried one of the refused upstream routing fields.
routing_override_not_permittedThe override tried to enable a substitution mode the profile does not permit.
auto_not_enabledauto was requested but no rung enables it.

Upstream-side failures return a provider-neutral envelope with the upstream's own detail attached under dz_upstream. Two outcomes worth knowing by name:

  • 502 unknown_upstream -- the request named an upstream connector this installation has not enabled.
  • 503 upstream_undetermined -- the routing profile could not be read and the installation has more than one upstream registered, so the request is refused rather than defaulted. It is retryable.

On this page