Error Contracts & Status Codes
The full gateway error taxonomy, its shared envelope, the x-ace-error header that tells an ACE refusal from a relayed vendor error, guardrail detection values, and the fail-open guarantee that keeps ACE's own accounting out of your error path.
Errors share one envelope:
{"error": {"message": "...", "type": "invalid_developer_key", "code": 401}}
| Type | HTTP | Meaning |
|---|---|---|
invalid_developer_key |
401 | Missing, invalid, revoked, or expired ACE developer key. MoreMissing, invalid, revoked or expired ACE developer key. There is no partial-credit anonymous fallback — a header that does not resolve to a known key fails the same way as no header at all. |
onboarding_incomplete |
402 | Missing provider key (stored/pass-through) or unconfigured model/router. MoreAuthenticated, but there is nothing to serve the request with: no provider key (stored or pass-through), or no model given and no router configured. |
model_not_in_scope |
403 | Requested model is not enabled for this tenant. MoreThe requested model is not enabled for this tenant. |
invalid_admin_key |
403 | Admin-only endpoint called with a non-admin key. MoreAn admin-only endpoint was called with a key that is not an admin key. |
rate_limited |
429 | Per-tenant RPS cap exceeded. |
budget_exhausted |
402 | Real-time spend cap breached; returns reset time and usage headers. MoreThe request would breach a real-time spend cap. The cap, what was consumed and when it resets ride onx-ace-budget-cap-usd, x-ace-budget-consumed-usd and x-ace-budget-resets-at as well as in the body. |
guardrail_violation |
400 | Prompt-injection or jailbreak guardrail tripped (names detected rule). MoreA prompt-injection or jailbreak guardrail tripped. Carries adetected field naming what fired. |
adapter_unavailable |
507 | LoRA adapter cannot fit into memory even when loaded alone. MoreA LoRA adapter cannot fit even when loaded alone. |
overloaded |
503 | Gateway capacity shed: ingress budget, loop stall, queue wait, or dispatch limit. MoreACE shed the request on its own capacity, never the provider's. Three places decide it: the transport, before the body is read (declared size against what is in flight,ACE_INGRESS_BUDGET, off unless set; or a heavy body while the event loop has been stalled past ACE_LOOP_STALL_SHED_SHARE of the last ACE_LOOP_STALL_WINDOW_S, on by default at 0.5 over 10s — light bodies are still served); engine entry, when the request sat past ACE_QUEUE_WAIT_MAX_S (10s) before the engine reached it; and dispatch, when every destination was at its concurrency limit. Always carries Retry-After, computed from what this process has actually drained lately (1–30s). The body wears the surface's own overload envelope (overloaded_error on the Anthropic surfaces). Back off and retry, or send direct to the provider — neither the key nor the provider is involved. GET /healthz reports the gauge under load. |
upstream_truncated |
502 | Provider returned truncated non-JSON 2xx; avoids double-billing on retry. MoreThe provider answered 2xx with a body that does not parse as JSON — a gzip stream or an EOF-framed body cut short between the provider's origin and ACE, which the HTTP client decodes without error. It is never relayed as the 200 it arrived with. Typed because its remedy differs from every other 502: the provider generated and billed this turn once, so a retry pays for the answer twice. On the relays only (/v1/messages, and every surface relaying a pass-through vendor key); the engine's routed path already falls forward on the same failure. |
store_timeout |
504 | Durable storage or BYOK store read timed out (retry with Retry-After: 5). MoreACE's own durable store did not answer inside its bound — a read behind a control-plane route (GET /api/v1/tenant/requests; retry with a narrower window or filters), or a BYOK store call on the request path that did not come back inside ACE_BYOK_STORE_TIMEOUT_S (default 30s, worker wait included). Carries Retry-After: 5 — the identity read timed out, so retry once the store answers. |
gateway_timeout |
504 | Request deadline exceeded (ACE_REQUEST_DEADLINE_S); safe to retry. MoreThe request deadline: the handler had not started its responseACE_REQUEST_DEADLINE_S (default 600s) after the transport accepted the request, and the gateway cancelled it and answered in its place. Nothing had been sent, so a retry is safe. The body wears the surface's envelope (api_error on the Anthropic surfaces, DEADLINE_EXCEEDED on Gemini, ModelTimeoutException on Bedrock), and x-ace-stage-durations beside it names the stages that completed and the one still running — byok_store=30000 is the store, upstream=… is the vendor. Once the status line has left no header can change, so a stream silent for ACE_STREAM_IDLE_S (default 300s) or open past ACE_STREAM_DEADLINE_S (default 3600s) is closed with one terminal error event carrying "type": "gateway_timeout" instead. |
/v1/execute differs. A validation failure is {"detail": {"error": "...", "field": "input.messages"}} and a locked-skill override is {"detail": {"error": "skill_locked", "locked": [...]}}. Both still carry x-ace-error (request_error and skill_locked respectively).
x-ace-error — whose error this is. Every error ACE originated carries the response header x-ace-error: <type>, on every surface (/v1/chat/completions, /v1/responses, /v1/execute, /anthropic/v1/messages and count_tokens, /v1/messages, /azure/openai/…, /gemini/v1beta/…, /bedrock/…), whatever envelope the body wears — the value is the type from the table above, plus the values below for refusals that have no typed envelope of their own. It is absent on a vendor error relayed from upstream — a real provider 429, a provider 401 for a bad pass-through key, a ValidationException AWS itself returned — so absence is the signal: no x-ace-error, the provider said this, and its own type and code mean what they say. A retry classifier that reads the header first can terminate on a provider auth error while re-minting an ACE key on invalid_developer_key, and back off on a provider rate limit while surfacing budget_exhausted to a human. The header only ever adds: no body, status or type changed when it was introduced. Engine-raised 5xx that summarize an upstream failure — 502 all upstreams failed, an upstream read timeout, a dispatch-side shed — do not carry it. Four 5xx do, because each is ACE's decision about ACE rather than a report about the provider: 503 overloaded (ACE's own capacity), 504 store_timeout (ACE's own store), 502 upstream_truncated (the provider's 2xx arrived unparseable and ACE refused to relay it) and 504 gateway_timeout (ACE's clock). A failure after the status line has left — a mid-stream error event — cannot carry a header at all.
x-ace-error |
HTTP | Meaning |
|---|---|---|
request_error |
400 | The request was refused as sent — a body that does not parse, a missing or malformed field, a /v1/execute validation failure, an unknown x-ace-skills pair, a Bedrock path/body modelId mismatch, an API key sent beside an access-key pair, a mode this deployment cannot serve. |
skill_locked |
403 | A per-request skill override (skill_overrides or x-ace-skills) on a skill the org has locked. |
forbidden |
403 / 451 | ACE's own policy refusal — a dedicated deployment serving another tenant, a data-residency or geo-fence decision. |
internal_error |
500 | An uncaught failure on a route with no quiet-passthrough relay. /v1/chat/completions, /v1/execute, /anthropic/v1/messages, /v1/messages and the /azure/, /gemini/ and /bedrock/ surfaces never produce it: there, an internal fault relays the request to the provider instead (the fail-open guarantee below). |
idp_authentication_error |
401 | A deployment fronted by a corporate identity provider refused the caller's IdP token. |
idp_forbidden_error |
403 | A deployment fronted by a corporate identity provider accepted the caller's IdP token but its group mapping grants no access. |
Guardrail detected values. Regex stage: instruction_override, role_reassignment, secret_exfiltration, system_prompt_probe. Classifier stage: learned_injection. The four regex values come from the always-on stage. learned_injection comes from the optional classifier and only appears on a deployment that has moved it out of its default shadow mode (ACE_GUARDRAILS_MODEL_MODE=enforce). In shadow — the default — the classifier never produces a 400 and never alters the response.
ACE never blocks traffic on its own accounting. No spend threshold, quota or internal limit turns a request into an error. Whenever ACE cannot compute — a metering ceiling, a telemetry blip, a skill that fails to evaluate — it degrades to direct passthrough: relayed straight to your provider, pipeline bypassed, normal 200, upstream body untouched. Errors originate at the edge (auth) or upstream (provider), never from the optimization layer.
Bypass triggers: Skill latency ceiling exceeded — a stage that has not answered inside its per-request wall-clock ceiling (250 ms acting, 50 ms shadow) is shed and the response names it: ceiling_exceeded, overloaded or loop_stalled on that stage's header and trace. Per request, never cumulative; nothing to reset.; Gateway panic / uncaught 500; Redis, Qdrant or TimescaleDB unreachable. Observable as: x-ace-served-by: direct_passthrough; x-ace-warnings: