> ## Documentation Index
> Fetch the complete documentation index at: https://docs.knoxcall.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors

> Every refusal the AI Gateway can return, the one envelope it arrives in, and which of them are safe to retry.

# 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:

```json theme={"dark"}
{
  "error": "budget_exceeded",
  "error_description": "Daily budget exceeded: $50.0031 ≥ $50",
  "code": "budget_exceeded"
}
```

`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.

<Note>
  **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.
</Note>

The endpoint and this envelope are in the OpenAPI spec as
`POST /v1/ai/{agentSlug}/{upstreamPath}` — see
[Execute AI Request](/api-reference/ai-gateway/execute).

### 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:

```
event: error
data: {"error":"upstream_stream_failed","error_description":"The response stream failed after it had started. Some of the response may be missing; the request was not completed.","code":"upstream_stream_failed","request_id":"0d5b2a9e-…"}
```

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:

```json theme={"dark"}
{
  "error": {
    "type": "not_found",
    "message": "Gateway not found.",
    "request_id": "0d5b2a9e-1f3c-4a7d-8e2b-6c9a1f4d7e35"
  }
}
```

`error.type` is the machine field. See the
[control-plane overview](/api-reference/ai-gateway/control-plane-overview#errors).
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:

```json theme={"dark"}
{
  "jsonrpc": "2.0",
  "id": 1,
  "error": { "code": -32601, "message": "Method not found" }
}
```

| Code     | Meaning                                                                                                                                                                                                                                    |
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `-32600` | Invalid request.                                                                                                                                                                                                                           |
| `-32601` | Method not found — including `resources/*` and `prompts/*`, which the gateway deliberately does not proxy ([why](/ai-gateway/mcp-gateway#resources-and-prompts-are-refused)).                                                              |
| `-32001` | Unauthenticated, or the tool is not allowed.                                                                                                                                                                                               |
| `-32002` | Blocked by the prompt firewall.                                                                                                                                                                                                            |
| `-32000` | A governance refusal — a spend cap, a token scope limit, a guardrail verdict. The HTTP status carries the class (`429` for a cap, and it carries `Retry-After`) and the message is the same sentence the `/v1/ai` refusal would have used. |
| `-32603` | Internal error — including a paused or archived agent or gateway.                                                                                                                                                                          |

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

| Status  | Meaning here                                                                              | Retry?                                                                                                   |
| ------- | ----------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| **400** | Your request was refused by a policy you configured.                                      | No — nothing changes on a retry.                                                                         |
| **401** | The credential was rejected.                                                              | No — mint a new token.                                                                                   |
| **403** | The credential is valid but not permitted to do this.                                     | No.                                                                                                      |
| **404** | No agent with that slug for this tenant.                                                  | No.                                                                                                      |
| **422** | The request was understood and is unprocessable (schema violation, unsupported option).   | No.                                                                                                      |
| **429** | A ceiling was reached — budget, token cap, or request rate.                               | Yes — **honour `Retry-After`**, which every 429 on this plane now carries.                               |
| **500** | A fault on our side.                                                                      | Yes, with backoff.                                                                                       |
| **502** | The upstream provider failed.                                                             | Yes, with backoff.                                                                                       |
| **503** | A control could not be applied, so the request was refused rather than served ungoverned. | Yes, but it will keep failing until the underlying configuration or dependency is fixed — read the code. |

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.

| Code                                                                                                      | Status    | What happened                                                                                                                                                                                                                  |
| --------------------------------------------------------------------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `missing_token`                                                                                           | 401       | No credential on the request.                                                                                                                                                                                                  |
| `malformed`, `bad_prefix`, `bad_env`, `bad_kind`, `bad_random`, `bad_crc_format`, `crc_mismatch`, `empty` | 401       | The token did not parse. `bad_prefix` is the common one after the 2026-09-01 rename — a `kc_`-prefixed AI token no longer parses; re-mint.                                                                                     |
| `unknown_token`                                                                                           | 401       | Parsed, but matches no record.                                                                                                                                                                                                 |
| `capability_hmac_mismatch`                                                                                | 401       | The token's scope does not match its signature.                                                                                                                                                                                |
| `tenant_key_missing`                                                                                      | 401       | The tenant master key was unavailable.                                                                                                                                                                                         |
| `dpop_missing`, `dpop_jkt_missing`                                                                        | 401       | The token is DPoP-bound and the proof was absent or unbindable. Sign `htu` against the **canonical public URL**, not a rewritten `Host`.                                                                                       |
| `oneshot_already_used`                                                                                    | 401       | A one-shot token was replayed. It is consumed after authentication but *before* policy, so a call refused by a policy has still spent it. Mint one per attempt.                                                                |
| `oneshot_consume_error`                                                                                   | 401       | The consume could not be recorded, so the call was refused rather than risk a replayable one-shot.                                                                                                                             |
| `agent_missing`                                                                                           | 401       | The token names no agent — an agentless `tool` token presented to `/v1/ai`, which requires one.                                                                                                                                |
| `outside_valid_window`                                                                                    | 403       | Presented outside the token's `valid_window`.                                                                                                                                                                                  |
| `ip_cidr_mismatch`                                                                                        | 403       | Presented from outside the token's `ip_cidrs`.                                                                                                                                                                                 |
| `tenant_unavailable`                                                                                      | 403       | The workspace is not currently servable.                                                                                                                                                                                       |
| `step_up_required`                                                                                        | 401       | The token was flagged by [impossible-travel detection](/ai-gateway/tokens-and-dpop#impossible-travel-detection-and-step-up).                                                                                                   |
| `invalid_token`, `insufficient_scope`, `invalid_target`                                                   | 401 / 403 | The MCP audience guard: the token carries no authenticated scope, is not `tool`-kind, or is bound to a different MCP resource. Carries an RFC 6750 `WWW-Authenticate` challenge with the RFC 9728 `resource_metadata` pointer. |
| `unknown_mcp_resource`                                                                                    | 404       | The path is not an MCP resource on this host.                                                                                                                                                                                  |
| `missing_auth`                                                                                            | 401       | The request reached the rate limiter or the handler with no verified token — a mount error, refused rather than served unmetered.                                                                                              |

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

| Code                                                   | Status | What happened                                                                                                                     |
| ------------------------------------------------------ | ------ | --------------------------------------------------------------------------------------------------------------------------------- |
| `agent_not_found`                                      | 404    | No agent with that slug for this tenant.                                                                                          |
| `agent_token_mismatch`                                 | 403    | The token belongs to a different agent.                                                                                           |
| `agent_not_active` / `gateway_not_active`              | 503    | The agent, or its gateway, is paused or archived.                                                                                 |
| `agent_not_configured` / `agent_route_missing`         | 503    | The agent has no usable upstream route.                                                                                           |
| `route_env_mismatch`                                   | 403    | A Test token addressed a Live route, or the reverse.                                                                              |
| `route_field_actions_unsupported`                      | 503    | The route carries Relay field actions, which `/v1/ai` cannot apply. Refused rather than silently skipped.                         |
| `route_signature_unsupported`                          | 503    | The route requires a rolling URL signature, which `/v1/ai` cannot compute.                                                        |
| `agent_route_misconfigured` / `credential_unavailable` | 503    | The provider credential could not be resolved.                                                                                    |
| `linked_secret_domain_violation`                       | 403    | A shared secret's domain lock refused this upstream. Audited on **both** tenants.                                                 |
| `residency_violation`                                  | 403    | The request would have left its [pinned region](/ai-gateway/data-residency).                                                      |
| `upstream_error`                                       | 502    | The provider failed after every configured failover hop.                                                                          |
| `upstream_stream_failed`                               | —      | A STREAM failed after its first frame. There is no status left to change, so it arrives as the terminal SSE `event: error` above. |
| `ai_gateway_unavailable`                               | 503    | The plane failed to start. A platform fault, not your request. Carries `Retry-After`.                                             |

### Model, tools and output

| Code                                     | Status | What happened                                                                                                                                                                                            |
| ---------------------------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model_denylisted` / `model_not_allowed` | 403    | The requested model is denied, or not on the agent's allowlist.                                                                                                                                          |
| `tool_not_allowed`                       | 403    | A tool outside the agent's `tool_allowlist`.                                                                                                                                                             |
| `batch_input_not_inspectable`            | 422    | A batch envelope the gateway cannot open — and it will not forward what it cannot screen.                                                                                                                |
| `output_schema_violation`                | 422    | The response failed the agent's `output_schema` and the action is `block`. Under `warn` the response is served with `X-Knox-AI-Output-Warning`; a retried call carries `X-Knox-AI-Retry: output_schema`. |

### Firewall

See [Prompt firewall](/ai-gateway/firewall).

| Code                          | Status | What happened                                                                                                                                                                       |
| ----------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `firewall_block`              | 400    | A rule matched under a policy whose action is `block`.                                                                                                                              |
| `firewall_policy_unavailable` | 503    | The agent's policy could not be loaded.                                                                                                                                             |
| `firewall_policy_missing`     | 503    | The agent references a policy that no longer exists. Re-attach one, or clear `firewall_policy_id`.                                                                                  |
| `firewall_policy_degraded`    | 503    | One or more rules failed to compile, so a `block` policy is only partly applied. On `warn`/`tag` the request proceeds and the degradation is written to your firewall event ledger. |

### Budgets and token caps

See [Budgets & FinOps](/ai-gateway/budgets-finops).

| Code                                                                                                             | Status | What happened                                                                                                                                                                                                                                                                                                                                                                                                                       |
| ---------------------------------------------------------------------------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `budget_exceeded`                                                                                                | 429    | The agent's (or its gateway's — the message is prefixed `Gateway budget:`) daily or monthly cap is spent. Carries `Retry-After`: the seconds to that cap's UTC rollover.                                                                                                                                                                                                                                                            |
| `budget_per_call_exceeded`                                                                                       | 400    | This one request asks for more than `budget_per_call_max_tokens`.                                                                                                                                                                                                                                                                                                                                                                   |
| `budget_unenforceable`                                                                                           | 503    | The spend counter is unreadable, and the agent's action is `block` — so the request was refused rather than served unmetered. Responses in this state carry `X-Knox-AI-Budget-Unmetered: 1`.                                                                                                                                                                                                                                        |
| `budget_unpriced_model`                                                                                          | 403    | The agent has a HARD spend cap and the model it would run on has no price in KnoxCall's pricebook — so the call would not move the spend counter and the cap could never bind. Refused PRE-flight, before the provider is paid. Add the model to the pricebook, choose a priced one, or set `budget_overage_action` to `warn`. Structural for `openai-compatible`, `azure-openai` and `ollama`, whose endpoint and model are yours. |
| `token_scope_limit_exceeded`                                                                                     | 429    | The **token's own** signed daily cost or token cap is spent. Carries `Retry-After` to the next 00:00 UTC.                                                                                                                                                                                                                                                                                                                           |
| `token_scope_malformed`                                                                                          | 403    | The token's scope carries an unusable limit. Re-mint.                                                                                                                                                                                                                                                                                                                                                                               |
| `token_scope_unattributable`                                                                                     | 403    | The call cannot be attributed, so a token cap cannot be counted against it.                                                                                                                                                                                                                                                                                                                                                         |
| `token_scope_unenforceable`                                                                                      | 503    | The counter or pricebook needed to enforce a signed cap is unavailable.                                                                                                                                                                                                                                                                                                                                                             |
| `token_scope_check_failed`                                                                                       | 503    | The signed cap could not be read at all. Refused rather than served unmetered.                                                                                                                                                                                                                                                                                                                                                      |
| `token_scope_unpriced_model`                                                                                     | 403    | The model is not in the pricebook, so a signed cost cap could not be applied.                                                                                                                                                                                                                                                                                                                                                       |
| `provider_not_in_token_scope`, `model_not_in_token_scope`, `tool_not_in_token_scope`, `route_not_in_token_scope` | 403    | The token's capability scope does not cover this call.                                                                                                                                                                                                                                                                                                                                                                              |
| `malformed_token_scope`                                                                                          | 403    | The scope is not a usable restriction — refused rather than read as "unrestricted".                                                                                                                                                                                                                                                                                                                                                 |
| `rate_limit_exceeded`                                                                                            | 429    | A request-rate ceiling. Carries `Retry-After`. See [Limits](/ai-gateway/limits).                                                                                                                                                                                                                                                                                                                                                    |

### PII and guardrails

See [PII redaction](/ai-gateway/pii-redaction) and
[Guardrail webhook](/ai-gateway/guardrail-webhook).

| Code                                             | Status | What happened                                                                                                                                                                                                                                                                                                                                                                     |
| ------------------------------------------------ | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pii_policy_unavailable` / `pii_policy_degraded` | 503    | The agent's PII policy could not be loaded, or a recognizer failed to compile. Refused rather than served with redaction partly off.                                                                                                                                                                                                                                              |
| `pii_tokenize_failed`                            | 503    | Tokenization failed, so the prompt was not forwarded. The raw value never leaves.                                                                                                                                                                                                                                                                                                 |
| `pii_detokenize_unavailable`                     | 503    | The token map needed to restore values was unavailable.                                                                                                                                                                                                                                                                                                                           |
| `guardrail_block`                                | 400    | Your guardrail webhook returned a blocking verdict.                                                                                                                                                                                                                                                                                                                               |
| `guardrail_streaming_unsupported`                | 400    | A guardrail configuration that cannot be applied to a streamed response.                                                                                                                                                                                                                                                                                                          |
| `streaming_redaction_unsupported`                | 400    | The agent has a PII policy and the provider has no mid-stream redactor, so a streamed response could not be redacted. Refused rather than streamed in the clear. Retry buffered (drop `stream: true` and `Accept: text/event-stream`).                                                                                                                                            |
| `pii_unscannable_body`                           | 400    | The request body is a shape the PII walker cannot open (a compressed or file-referenced batch), so it could not be screened. The gateway does not forward what it cannot screen.                                                                                                                                                                                                  |
| `pii_tokenize_failed`                            | 503    | Tokenization threw. The prompt was not forwarded — the raw value never leaves.                                                                                                                                                                                                                                                                                                    |
| `canary_leak`                                    | 400    | The provider's response echoed this agent's system-prompt canary, which is proof the system prompt was extracted by the prompt in this request — not a heuristic guess. Refused when the attached firewall policy's action is `block`; under `warn` the response is served. Either way the marker itself is spliced out of what you receive, on the buffered path and mid-stream. |

## 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.
