Skip to main content

Headers and Error Codes

AISIX returns responses through several caller-facing API formats. A failed chat-completions request, an Anthropic-style messages request, and a passthrough route request do not all use the same error envelope.

This reference helps interpret response headers, retry hints, status codes, and error fields. Start with the request path, then read the matching error format before deciding whether the failure is caller-side, gateway-side, or upstream-provider-side.

Error Response Formats

Use the request path to identify the error envelope that applies.

Response came fromError envelopeRead
OpenAI-compatible proxy routes such as /v1/chat/completions, /v1/completions, /v1/embeddings, /v1/responses, audio, images, and rerank{"error": {...}}OpenAI-Style Proxy Errors
Anthropic-style proxy routes such as /v1/messages and /v1/messages/count_tokens{"type":"error","error": {...}}Anthropic-Style Proxy Errors
MCP routes under /mcp and /mcp/{server}JSON-RPC error envelopeMCP Errors
A2A calls under /a2a/{agent}Upstream JSON-RPC response; HTTP errors before forwarding; JSON-RPC error for an upstream dispatch failureA2A Errors
A2A agent cards under /a2a/{agent}/.well-known/agent-card.jsonAgent-card JSON on success; HTTP error response on gateway or upstream failureA2A Errors
Configured passthrough routesForwarded upstream status and body, or {"error": {...}} for AISIX-generated failuresPassthrough Errors

Proxy Response Headers

Operational headers vary by endpoint. Do not treat every header as universal across every /v1/* route.

HeaderWhen to use it
x-aisix-call-idAppears on chat-completions responses. Use it to correlate one gateway call.
x-aisix-request-idAppears on every proxy response. Use it to correlate the response with any access logs and usage events the request produces. Some MCP failures occur before usage accounting; see Usage Events. Accepted on the request by default: send your own ID and AISIX uses that value throughout instead of generating one. Which request headers an ID is read from is configurable, so a deployment can extend or disable this. See Reuse Your Own Request ID.
x-aisix-served-byAppears on successful chat-completions routing responses. Use it to identify the target model that served the request.
x-aisix-cacheAppears on chat requests covered by a cache policy: hit, miss, or bypass. Only Cache-Control: no-cache produces bypass; no-store still performs a lookup and therefore reports hit or miss. Use it to check whether the gateway served the response from cache.
x-aisix-cache-layerAppears on cache hits: exact for an identical-request match, semantic for an embedding-similarity match. Use it to attribute hits between the two matching layers.
x-aisix-cache-similarityAppears on semantic cache hits. The cosine similarity of the matched entry, 01. Use it to calibrate a policy's similarity threshold.
x-ratelimit-*The per-dimension family, with names ending in -requests, -tokens, and -concurrent. Appears on successful chat-completions responses when the caller API key has rate limits configured. Use it to inspect request, token, and concurrent limit state where applicable.
X-RateLimit-Limit
X-RateLimit-Remaining
X-RateLimit-Reset
X-RateLimit-Scope
Appears only on a 429 produced by the AISIX rate limiter itself. Use it to learn which limit was hit and when to retry. See Rate-Limit Rejection Headers.
Retry-AfterAppears on rate-limit, budget, and all-candidates-unavailable rejections when the gateway has a retry hint. Also appears on upstream 429 responses when AISIX can parse the upstream retry hint. Use it to tell callers when to retry.

Rate-Limit Rejection Headers

When the AISIX rate limiter itself refuses a request, the 429 describes the limit that refused it, in both the OpenAI-style and Anthropic-style error envelopes.

HeaderValue
X-RateLimit-LimitThe configured cap of the limit that refused the request.
X-RateLimit-RemainingHeadroom left under that cap. 0 on a rejection.
X-RateLimit-ResetSeconds until that limit accepts requests again.
Retry-AfterThe same number of seconds, so a client can back off on whichever header it already supports.
X-RateLimit-ScopeAn AISIX extension naming the limit that refused: rps, rpm, rph, rpd, tpm, tpd, or concurrency.

X-RateLimit-Reset is a duration in seconds, not a timestamp, so a client does not need a clock synchronized with the gateway. For a windowed limit it counts down toward the end of the current fixed window. It is never 0; the smallest value is 1.

A caller API key, a model, and a rate-limit policy can each carry several limits at once. Exactly one of them refuses a given request — the first one AISIX finds exhausted — and the headers describe that one. X-RateLimit-Scope is what makes the numbers unambiguous, because the units differ: x-ratelimit-limit: 1000 is 1000 requests under rpm, 1000 tokens under tpm, and 1000 in-flight requests under concurrency.

Two cases do not follow that reading:

  • A routing model or semantic router. Dispatch skips each target that is over its own limit and tries the next one; when none is left, the headers describe the last target that refused, which is a limit configured on that target rather than on the alias the request addressed. Read them as "this is why the request could not be placed", not as the caller's own quota.
  • An ensemble model. A panel member or judge that exceeds its own limit fails the request with a 429 that carries none of these headers, and no Retry-After either.

A concurrency rejection is the one case with no fixed window. A slot frees when some in-flight request finishes, which the gateway cannot predict, so X-RateLimit-Reset and Retry-After both report a fixed 60.

A raw rejection looks like this:

HTTP/1.1 429 Too Many Requests
content-type: application/json
retry-after: 43
x-ratelimit-limit: 100
x-ratelimit-remaining: 0
x-ratelimit-reset: 43
x-ratelimit-scope: rpm
x-aisix-request-id: 018f3c1f-...

{"error":{"message":"request limit exceeded (requests)","type":"rate_limit_exceeded"}}

HTTP header names are case-insensitive, and AISIX writes them in lowercase on the wire. A client reading X-RateLimit-Limit and a client reading x-ratelimit-limit both match.

The X-RateLimit-* headers appear only when AISIX itself refused the request. They are absent from:

  • Successful responses. A 200 carries the per-dimension x-ratelimit-* family instead: x-ratelimit-limit-requests, x-ratelimit-limit-tokens, x-ratelimit-limit-concurrent, and their remaining and reset counterparts. That family reports the caller API key's own rpm, tpm, and concurrency state only — not a model limit, not a rate-limit policy, and not the per-second, per-hour, or per-day windows.
  • Upstream 429 responses. AISIX forwards the provider's Retry-After when it can parse one, but the provider's quota state is not something AISIX knows, so it does not report one of its own.
  • Budget rejections. A budget caps spend in currency, not requests or tokens. It shares the 429 status and carries structured budget fields inside the error body, plus Retry-After when the control plane supplied a reset time.

Their presence is therefore the signal that the AISIX limiter itself rejected the request. Retry-After alone is not that signal: as the two cases above show, it appears on other rejections too.

Proxy Status Codes

Use the error type first when the envelope includes one. The status code gives the broad category, and the error type usually identifies the more precise gateway condition.

StatusMeaning
400The request is invalid.
401Caller authentication is missing or invalid.
403The caller is authenticated but is not permitted by the configured access controls.
404The requested resource was not found. Gateway-generated examples include an unknown model alias, MCP server, or A2A agent. An upstream service can also return 404; AISIX preserves upstream 4xx statuses.
413The request body exceeds the configured proxy request body limit.
422Content was blocked by policy.
429The request hit a rate limit or budget rejection.
501The resolved provider adapter does not implement the requested endpoint.
502The upstream provider returned a server-side failure or the provider adapter mapped an upstream failure into the proxy error format.
503An authentication dependency or provider adapter is unavailable, or every routing candidate was filtered out by runtime status.
504The upstream request timed out.

OpenAI-Style Proxy Errors

AISIX OpenAI-compatible proxy errors use this envelope:

{
"error": {
"message": "...",
"type": "invalid_request_error"
}
}

The param and code fields are omitted when AISIX has no value for them. Budget denials include structured budget fields inside the error object, such as scope, limit_usd, spent_usd, period, and retry_after_seconds.

Common AISIX error.type values are:

Error typeTypical statusMeaning
invalid_api_key401Caller authentication is missing or invalid. This type also covers an invalid, expired, or unmapped JWT; inspect error.code for the specific condition.
permission_denied403The caller API key cannot use the requested model, the caller's client IP is outside the model's allowed_cidrs, or a verified JWT does not satisfy required scopes or claims.
model_not_found404The requested model alias is not configured.
invalid_request_error400 or 413The request body or endpoint usage is invalid. Oversized OpenAI-style requests return this error type with status 413.
provider_unavailable503The selected upstream provider adapter cannot complete the request.
all_candidates_unavailable503Every routing candidate was filtered out or unavailable.
api_error503AISIX could not complete an internal dependency operation, such as fetching signing keys for JWT authentication.
content_filter422The request or response was blocked by policy.
billing_error429The request was rejected by billing or budget state.
rate_limit_exceeded429The request exceeded a configured rate limit.
not_implemented501The resolved provider adapter does not implement the requested endpoint.
timeout504The upstream request timed out.
upstream_errorVaries, often 502 for upstream server-side failuresThe upstream provider returned an error that AISIX rendered through the proxy error format.

Authentication Error Codes

OpenAI-style proxy errors can include these stable error.code values for caller authentication. Anthropic-style proxy errors omit code; use their HTTP status and status-mapped error.type instead.

error.codeStatusMeaning
api_key_expired401The caller API key's expiration deadline has passed.
api_key_disabled401The caller API key is administratively disabled.
jwt_invalid401The JWT is malformed or fails issuer, signature, signing algorithm, audience, required-claim, or not-before validation.
jwt_expired401The JWT's exp deadline has passed.
jwt_claims_rejected403The JWT is valid but does not satisfy the provider's required scopes or bound claims.
jwt_identity_unmapped401The configured identity claim is missing or does not map to a caller API key bound to that OIDC provider.
jwks_unavailable503AISIX could not resolve or fetch the provider's signing keys. Check the OIDC discovery or JWKS endpoint, then retry.

For JWT trust-provider configuration and rejection behavior, see JWT Authentication.

Upstream Provider Errors

OpenAI-style routes render upstream provider failures through the same error envelope, but AISIX does not always return the upstream response unchanged.

Upstream 4xx responses keep the client-visible HTTP class. Native OpenAI upstream errors can keep their OpenAI-style fields. Cross-provider upstream errors use upstream_error and can include a more specific error.code, such as a rate-limit, permission, or model-not-found code.

Upstream 5xx responses generally return 502. AISIX does not expose upstream 5xx response bodies because they can contain provider account, infrastructure, or private diagnostic detail.

Anthropic-Style Proxy Errors

POST /v1/messages and POST /v1/messages/count_tokens use the Anthropic-style error envelope:

{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "..."
}
}

The Anthropic envelope omits the param and code fields that the OpenAI envelope can carry.

The nested error.type follows Anthropic SDK-compatible status mappings:

StatusAnthropic error.type
400 or 422invalid_request_error
401authentication_error
403permission_error
404not_found_error
408timeout_error
413request_too_large
429rate_limit_error
503overloaded_error
Other status codesapi_error

AISIX keeps the 408 mapping for Anthropic SDK compatibility. Gateway-originated timeouts usually surface through provider-error handling rather than as a native 408 response.

See Anthropic-Style Messages API for examples.

MCP Errors

ANY /mcp and ANY /mcp/{server} use MCP Streamable HTTP and JSON-RPC response shapes. Authentication and request-size failures can still use HTTP status codes such as 401 or 413 before the MCP handler runs.

When a guardrail blocks a tool call or tool result, AISIX returns HTTP 200 with a tool result marked isError. MCP reserves JSON-RPC protocol errors for a request that was not valid; a policy rejection is a tool-execution error, so the calling agent reads it as tool output and can adapt:

{
"jsonrpc": "2.0",
"id": 42,
"result": {
"content": [{ "type": "text", "text": "tool call blocked by content policy (guardrail 'block-secrets')" }],
"isError": true
}
}

This differs from OpenAI-compatible routes, where guardrail blocks use HTTP 422 with an OpenAI-style error envelope.

A rejected or unknown tool is a protocol error instead — HTTP 200 with error.code -32602 — because the request itself named something the caller may not call.

A2A Errors

POST /a2a/{agent} returns a successful upstream JSON-RPC response unchanged. After the gateway has identified the agent and entered A2A dispatch, an upstream connection failure or non-success response returns HTTP 502 with a JSON-RPC error envelope:

{
"jsonrpc": "2.0",
"id": 42,
"error": {
"code": -32000,
"message": "..."
}
}

Some failures occur before AISIX forwards the A2A request and therefore are not JSON-RPC errors. Missing or invalid caller authentication returns 401. A caller key without access to the registered agent returns 403, and an unknown or disabled agent returns 404. Rate-limit or AISIX Cloud budget rejections return 429.

Agent-card discovery uses ordinary HTTP status codes rather than a JSON-RPC envelope. Missing or invalid caller authentication returns 401, and an agent access denial returns 403. An unknown or disabled agent returns 404. If the gateway cannot fetch a usable card from the upstream agent, it returns 502. If the gateway cannot determine its own public address from the request, it returns 500 rather than a card that still advertises the upstream agent's address.

A streaming method (message/stream, tasks/resubscribe) follows the same rules only until the stream opens. Once the response has started, the status line is already sent, so a later upstream failure cannot become a 502: the gateway relays a JSON-RPC error envelope as the final event of the stream instead, and records the failure on the usage event. An upstream that answers a streaming call with a plain JSON-RPC response rather than a stream has that response relayed as a single event.

Passthrough Errors

A matched passthrough route forwards the upstream status code and body unchanged when AISIX receives an upstream HTTP response and no gateway policy replaces it.

Failures generated by AISIX use the OpenAI-style proxy error envelope. These failures include caller authentication rejection, a missing allowed_routes grant or a source outside the route's source_cidrs (403, with error.code: ip_restricted for the source rejection), a guardrail block (422), and an upstream transport, timeout, or response-decoding failure.

A /passthrough/* path that no explicit route claims follows the ordinary empty-body 404 path. Create a passthrough route to claim the path.