Errors
Errors use standard HTTP statuses and a stable machine-readable code.
Error shape
{
"error": {
"type": "billing_error",
"code": "insufficient_balance",
"message": "Available balance 0.02 is below the required reservation 0.05",
"request_id": "req_6f1c…"
}
}Branch on code for the specific case and on type for the kind of thing that went wrong, not on message. Messages may change; codes and types won't. Some errors include param naming the field at fault, such as model or ib.suggestion; a refused record write adds problems, a failed action event and logs. Every error response carries the X-Request-ID header, and request_id matches it: errors are in Logs too, and a request refused before dispatch is never charged.
Categories
type follows from the status, so a client that only needs “my fault, or retry” can branch on it without a table of codes. The names are Anthropic's, so code written for their SDK's error classes carries over.
| Type | Status | What to do |
|---|---|---|
invalid_request_error | 400, 409, 422 | Something in the request: fix it before sending it again. |
authentication_error | 401 | The key is missing, wrong or revoked. |
billing_error | 402 | The team can't pay for this request: add credit, or raise the budget. |
permission_error | 403 | The key, its Directives or the account don't allow it. |
not_found_error | 404 | No such route or resource. |
request_too_large | 413 | Shrink the body. |
rate_limit_error | 429 | Wait for Retry-After, then retry. |
overloaded_error | 503 | Temporary: retry with backoff. |
api_error | anything else | Something on our side: retry with backoff, and check the receipt first for a request that may have run. |
Codes
| Status | Code | What to do |
|---|---|---|
| 400 | invalid_request | Fix the request body or parameters. |
| 400 | unsupported_parameter | Remove the parameter, or pick a model that supports it. |
| 400 | context_limit_exceeded | Input plus max_tokens is over the model's context window, or the input is over your maxInputTokens Directive. Shorten the input or lower max_tokens. |
| 401 | invalid_api_key | Check the key, or create a new one. |
| 402 | insufficient_balance | Add credit or turn on auto reload. |
| 402 | budget_exceeded | Raise the budget in Directives, or wait for the next period. |
| 403 | policy_denied | Directives don't allow the model, its providers, or this request's maximum charge; the project or environment is disabled; or the key lacks a permission the route needs (such as Auditor Permissions). The message says which. |
| 404 | not_found | No such route: check the method and path. For an Auditor repository, install the GitHub App on it first. |
| 403 | account_suspended | Contact support. |
| 409 | idempotency_conflict | The Idempotency-Key was used with a different body. Use a new key. |
| 409 | request_in_progress | The original request with this key is still running. Wait for it. |
| 409 | result_not_replayable | The original request finished. Read its receipt instead of retrying. On the management API: the original call already made the key, rotation or webhook endpoint named in original_id; read it there. |
| 409 | version_conflict | Directives changed since you read them (management API). Read them again, apply your change to the current policy_version, and retry. |
| 413 | payload_too_large | Keep the body under the endpoint's limit: 1 MiB for inference, 64 KiB for the management API and for changing a repository's Auditor setup, 4 MiB for Review a change. |
| 429 | rate_limit_exceeded | Your Directives requests- or tokens-per-minute limit, not an outage. Wait for Retry-After seconds, or raise the limit (see Rate limits below). |
| 429 | concurrency_limit_exceeded | Your Directives concurrent-requests limit, not an outage. Wait for an in-flight request to finish, or raise the limit. |
| 429 | upstream_rate_limited | Every allowed provider was at capacity. Raising your limits won't help; retry after a short wait. |
| 503 | no_healthy_route | No allowed provider is available right now, or the model is temporarily disabled. Retry with backoff. |
| 503 | dependency_unavailable | Temporary platform issue. Retry with backoff. |
| 503 | upstream_funding_unavailable | Temporary platform issue. Retry with backoff. |
| 504 | request_deadline_exceeded | The provider didn't answer in time. Check the receipt before retrying; a generation may have completed. Stream long requests. |
Rate limits
A 429 comes from one of two places. rate_limit_exceeded and concurrency_limit_exceeded mean a limit in your Directives was reached; every team has defaults (600 requests and 2,000,000 tokens per minute, 10 concurrent) even if none were set. The X-RateLimit-Scope and X-RateLimit-Dimension headers name the level and the limit, and the message gives its value. upstream_rate_limited means the providers themselves were at capacity. Neither kind is charged.
Errors during a stream
Once a stream has started, the status is already 200 and can't change. If it fails partway, a last event carries the error and the stream closes without a [DONE] marker:
data: {"error":{"type":"api_error","code":"stream_interrupted","message":"…"}}Treat a stream without [DONE] as incomplete, and don't stitch output from separate requests together. The receipt still records what the provider billed for the partial answer.
Anthropic Messages
/v1/messages returns errors in Anthropic's layout, { "type": "error", "error": { "type", "code", "message" }, "request_id" }, so Anthropic SDKs handle them natively: the same type and code as above, with request_id at the top level instead of inside error. A stream that breaks ends with an event: error frame instead of a missing [DONE].
OpenAI Responses
/v1/responses returns errors in the shape above. A stream that breaks ends with an event: error frame, { "type": "error", "code": "stream_interrupted", "message" }, and no response.completed.