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.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: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 theX-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 actionai_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.