Skip to main content

Errors

The gateway refuses rather than degrades. Almost every entry on this page exists because serving the request would have meant applying less governance than you configured — so a refusal here is usually a control working, not a fault. This page is the whole surface in one place: the envelope you parse, the codes you can branch on, and what to do about each.

One envelope

Every refusal on the AI data plane — /v1/ai/* and everything in front of it: authentication, the MCP audience guard, the rate limiter, the boot sentinel — arrives in exactly this shape:
error and code always carry the same machine-readable identifier, so a client that reads either keeps working. error_description is always the human sentence — never branch on it; descriptions are rewritten whenever a clearer wording is found, identifiers are part of the contract. Some refusals add extra keys alongside the three (utilization_pct, model, request_id). Ignore keys you do not recognise; new ones are additive.
Shipped 2026-09-08 (AIGW-163). Until then three incompatible shapes answered this one URL and they disagreed about which field was machine-readable: the pipeline sent {error: "<code>", error_description}, the auth layer sent {error: "Unauthorized", code, reason} — the inversion — and the MCP audience guard sent a third reading in which error was a code that was not the code.If you wrote a client against the previous advice on this page, you read code and you are unaffected. reason is gone; its string is now error_description. error on an auth refusal is now the code rather than the word Unauthorized, and the X-Auth-Failure header still carries the same value.
The endpoint and this envelope are in the OpenAPI spec as POST /v1/ai/{agentSlug}/{upstreamPath} — see Execute AI Request.

Errors mid-stream

A streamed call commits its status line with the first frame, so a failure after that cannot be a status code. It arrives as a terminal SSE event carrying the same object:
Treat an SSE stream that ends without this frame and without the provider’s own terminal event as a transport truncation, and retry it.

The management plane is a different envelope

Everything under /v1/ai-gateway/* — creating gateways, agents, tokens, firewall policies, MCP servers, reading usage — is the Management API, not the data plane. It has its own credential and its own standard envelope, which is nested and is shared with every other /v1 resource:
error.type is the machine field. See the control-plane overview. This one is deliberately unchanged: unifying it would be a breaking change to every resource in the product to fix an inconsistency that only ever existed inside /v1/ai.

The MCP plane speaks JSON-RPC

/v1/mcp answers JSON-RPC 2.0, so its refusals are that protocol’s object, not the envelope above:
The one exception on that plane: a token presented to the wrong MCP resource is refused by the audience guard, which is an HTTP layer and answers the envelope at the top of this page.

Retry, or don’t

The 503s are the ones worth understanding: almost none of them mean “the gateway is down”. They mean the gateway could not prove it was enforcing what you asked for, and refused instead of quietly doing less.

Retry-After is exact, or absent

A 429 from a spend cap carries the whole number of seconds until that cap’s counter rolls over — daily budgets and signed token caps reset at 00:00 UTC, monthly budgets at the first instant of the next UTC month — because that is when the refusal genuinely stops applying. A rate-limit 429 carries the remainder of its window. Where the server does not know the answer it sends no header at all rather than a guess: a wrong value is worse than none, since a client backs off on it either way. So Retry-After is never zero and never fractional, and its absence on a 429 means “back off with your own schedule”, not “retry now”.

Authentication

The code is also returned in the X-Auth-Failure response header, which lets a client or a proxy classify the failure without reading the body. Every one of these now writes an ai_gateway.request_denied audit row, on both planes — except a request with no token or an unknown token, which has no workspace to attribute to and must not be able to write to your audit log.

Policy and pipeline

Credentials and routing

Model, tools and output

Firewall

See Prompt firewall.

Budgets and token caps

See Budgets & FinOps.

PII and guardrails

See PII redaction and Guardrail webhook.

What is recorded

Every refusal above writes one audit row under the single action ai_gateway.request_denied, with the specific cause in the row’s details. One action name for every denial is deliberate: a new refusal reason cannot invent an action nobody is alerting on. Alert on ai_gateway.request_denied and read the reason from the row. Every response — refusal or not — carries X-Request-Id. Quote it when you contact support; it is the key into the audit trail for that exact call.