Auriko returns errors in the format matching the endpoint’s API family. OpenAI-compatible endpoints (/v1/chat/completions, /v1/models, etc.) use the OpenAI error envelope. Anthropic-compatible endpoints (/v1/messages, /v1/messages/count_tokens) use the Anthropic error envelope.
Error envelope
OpenAI error envelope
Every non-2xx response on OpenAI-compatible endpoints uses this shape:
| Field | Type | Required | Purpose |
|---|
message | string | yes | Human-readable, actionable. Don’t branch on text. |
type | string | yes | One of six categories (see below). Drives SDK class. |
code | string | null | yes | Stable machine identifier. Branch on this. |
param | string | null | yes | Offending field name. null when not attributable. |
doc_url | string | recommended | Link to the error’s docs page. |
provider | string | null | no | Upstream provider that generated the error. Null for non-provider errors. |
suggestion | string | no | Actionable fix hint for routing, capability, or model-not-found errors. Absent for other error types. |
Notes:
- The envelope is flat under
error. There is no details[] array and no nested error objects.
Content-Type is application/json; charset=utf-8 on every error response.
- The response body is never empty; even 401 and 404 carry the envelope.
Anthropic error envelope
Anthropic-compatible endpoints (/v1/messages and /v1/messages/count_tokens) return errors in the Anthropic Messages API format:
| Field | Type | Required | Purpose |
|---|
type (top-level) | string | yes | Always "error". |
error.type | string | yes | One of nine categories (see table below). |
error.message | string | yes | Human-readable description. Don’t branch on text. |
error.suggestion | string | no | Actionable fix hint for routing, capability, or model-not-found errors. Absent for other error types. |
The Anthropic envelope doesn’t carry code, param, or doc_url. Routing, capability, and model-not-found errors include an optional suggestion with an actionable fix hint. Branch on error.type and HTTP status instead.
error.type | When |
|---|
invalid_request_error | 400 — malformed or invalid request |
authentication_error | 401 — missing or invalid API key |
permission_error | 403 — authenticated but not allowed |
not_found_error | 404 — resource doesn’t exist |
request_too_large | 413 — payload exceeds size limit |
rate_limit_error | 429 — rate or quota limit hit |
billing_error | 402 — billing issue |
api_error | 500 / 502 / 503 / 504 — server-side fault |
overloaded_error | 529 — temporarily overloaded |
Every response — success and error — carries:
x-request-id — unique per request. Copy this when opening a support ticket. SDKs expose it as request_id (Python) / requestId (TypeScript) on every raised exception.
Error responses also carry, when applicable:
Retry-After — integer seconds. Present on 429 and 503. SDKs read this for automatic backoff.
Error types (OpenAI envelope)
The OpenAI envelope’s type field uses a closed set of six values:
type | When | SDK class |
|---|
invalid_request_error | 400 / 405 / 409 / 413 / 422 — malformed or semantically invalid request | BadRequestError (or ConflictError on 409) |
authentication_error | 401 — missing, malformed, or invalid API key | AuthenticationError |
permission_error | 403 — authenticated but not allowed | PermissionDeniedError |
not_found_error | 404 — resource doesn’t exist or isn’t visible | NotFoundError |
rate_limit_error | 429 — rate or quota limit hit | RateLimitError |
api_error | 5xx — server-side fault or upstream failure | InternalServerError (500) / APIStatusError (502 / 503 / 504) |
Error codes
These codes are the canonical set, grouped by category. A published code’s meaning never changes. See the error-code reference for each code’s status and description.
Authentication and authorization
| Code | HTTP | type |
|---|
invalid_api_key | 401 | authentication_error |
expired_api_key | 401 | authentication_error |
insufficient_permissions | 403 | permission_error |
feature_disabled | 403 | permission_error |
mfa_required | 403 | permission_error |
invalid_recovery_code | 401 | authentication_error |
mfa_verification_failed | 401 | authentication_error |
Request validation
| Code | HTTP | type |
|---|
invalid_request | 400 | invalid_request_error |
missing_required_parameter | 400 | invalid_request_error |
invalid_parameter_value | 400 | invalid_request_error |
payload_too_large | 413 | invalid_request_error |
context_length_exceeded | 400 | invalid_request_error |
content_filtered | 400 | invalid_request_error |
idempotency_conflict | 409 | invalid_request_error |
idempotency_replay_unavailable | 409 | invalid_request_error |
field_immutable | 400 | invalid_request_error |
operation_not_allowed | 400 | invalid_request_error |
unknown_field | 400 | invalid_request_error |
method_not_allowed | 405 | invalid_request_error |
duplicate_resource | 409 | invalid_request_error |
state_precondition_failed | 409 | invalid_request_error |
Routing — capability
| Code | HTTP | type |
|---|
tools_not_supported | 400 | invalid_request_error |
json_mode_not_supported | 400 | invalid_request_error |
structured_output_not_supported | 400 | invalid_request_error |
tools_with_structured_output_not_supported | 400 | invalid_request_error |
vision_not_supported | 400 | invalid_request_error |
reasoning_not_supported | 400 | invalid_request_error |
thinking_disable_not_supported | 400 | invalid_request_error |
streaming_not_supported | 400 | invalid_request_error |
non_streaming_not_supported | 400 | invalid_request_error |
batch_only | 400 | invalid_request_error |
tier_opt_in_required | 400 | invalid_request_error |
tool_choice_required_not_supported | 400 | invalid_request_error |
no_compatible_endpoint | 400 | invalid_request_error |
no_responses_endpoint | 400 | invalid_request_error |
input_requires_responses_endpoint | 400 | invalid_request_error |
response_api_only | 400 | invalid_request_error |
hosted_tool_not_supported | 400 | invalid_request_error |
Routing — constraint
| Code | HTTP | type |
|---|
cost_constraint_exceeded | 400 | invalid_request_error |
latency_constraint_exceeded | 400 | invalid_request_error |
throughput_constraint_not_met | 400 | invalid_request_error |
provider_not_in_allowlist | 400 | invalid_request_error |
provider_blocked | 400 | invalid_request_error |
required_params_not_supported | 400 | invalid_request_error |
Routing — policy
| Code | HTTP | type |
|---|
byok_keys_required | 400 | invalid_request_error |
platform_keys_unavailable | 400 | invalid_request_error |
Routing — modality
| Code | HTTP | type |
|---|
unsupported_modalities | 400 | invalid_request_error |
Resources
| Code | HTTP | type |
|---|
model_not_found | 404 | not_found_error |
resource_not_found | 404 | not_found_error |
Rate limits and quotas
| Code | HTTP | type |
|---|
rate_limit_exceeded | 429 | rate_limit_error |
budget_exhausted | 429 | rate_limit_error |
insufficient_quota | 429 | rate_limit_error |
Routing and providers
| Code | HTTP | type |
|---|
no_provider_available | 503 | api_error |
upstream_error | 502 | api_error |
upstream_timeout | 504 | api_error |
model_unavailable | 503 | api_error |
client_disconnected | — | api_error |
Server
| Code | HTTP | type |
|---|
internal_error | 500 | api_error |
service_unavailable | 503 | api_error |
Auriko abstracts over multiple upstream LLM providers. Error messages name the model and the upstream provider that produced the error. The provider name appears in error.provider and may appear in error.message. Failover across providers happens before a 429 or 5xx surfaces to the client.
Retry policy
Branch on type and code, not on HTTP status alone. The SDK retry loop uses the same rules.
| Condition | Retryable | Notes |
|---|
type: rate_limit_error + code: rate_limit_exceeded | yes | Honor Retry-After header. |
type: rate_limit_error + code: budget_exhausted | no | Top up credits or raise the budget. |
type: rate_limit_error + code: insufficient_quota | no | Account has no quota on the current plan. |
type: api_error + status 500 / 502 / 503 / 504 (except code: internal_error) | yes | Exponential backoff. Honor Retry-After on 503. |
type: api_error + code: internal_error | no | Unclassified fault; retrying won’t help. Contact support with x-request-id. |
type: authentication_error / permission_error / not_found_error / invalid_request_error | no | Client-side fix required. |
| Network failure before any response (DNS, TCP, TLS) | yes | SDK raises APIConnectionError. |
Anthropic-compatible endpoints don’t carry code. The Anthropic SDK retries based on HTTP status.
Mid-stream errors (SSE)
Streaming endpoints return Content-Type: text/event-stream. When the HTTP status is already committed as 200 OK and an error occurs mid-stream, the envelope surfaces as a final data: event and the stream closes:
Rules:
- Errors never emit as partial JSON or a different SSE event name.
data: [DONE] signals successful completion only. After an error event, no [DONE] follows.
- The connection closes immediately after the error event.
SDK exception dispatch
The Python and TypeScript SDKs dispatch incoming envelopes to typed exceptions. Every instance exposes message, type, code, param, request_id, doc_url, status_code, retry_after_seconds, and provider.
Built-in retry
The SDK retries on APIConnectionError, rate_limit_error (except budget_exhausted and insufficient_quota), and api_error (except code: internal_error). Retries use exponential backoff and honor Retry-After when present.
Support
When reporting a failure to support, include the x-request-id from the response header (or request_id / requestId on the SDK exception). That single identifier pairs the client view with the server log.