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

# Errors

> Error envelope, codes, and retry behavior

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:

```json theme={null}
{
  "error": {
    "message": "Model 'gpt-5-turbo' is not in the catalog. See https://api.auriko.ai/v1/directory/models for available models.",
    "type": "not_found_error",
    "code": "model_not_found",
    "param": "model",
    "doc_url": "https://docs.auriko.ai/errors/model_not_found"
  }
}
```

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

```json theme={null}
{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "Request body must be a JSON object."
  }
}
```

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

## Required headers

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](/contract/error-codes) 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` |

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

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

<Note>
  Anthropic-compatible endpoints don't carry `code`. The Anthropic SDK retries based on HTTP status.
</Note>

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

```
HTTP/1.1 200 OK
Content-Type: text/event-stream
x-request-id: req_01HXABCDEFGHJKMNPQRSTVWXYZ

data: {"id":"chatcmpl_01HX...","choices":[{"delta":{"content":"Hello"},"index":0}]}

data: {"error":{"message":"Upstream timed out after 30 seconds.","type":"api_error","code":"upstream_timeout","param":null,"provider":"openai","doc_url":"https://docs.auriko.ai/errors/upstream_timeout"}}
```

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

<CodeGroup>
  ```python Python Auriko theme={null}
  import os
  from auriko import Client
  from auriko.errors import (
      AurikoAPIError,
      APIConnectionError,
      AuthenticationError,
      PermissionDeniedError,
      BadRequestError,
      ConflictError,
      NotFoundError,
      RateLimitError,
      InternalServerError,
      APIStatusError,
  )

  client = Client(api_key=os.environ["AURIKO_API_KEY"])

  try:
      response = client.chat.completions.create(
          model="gpt-4o",
          messages=[{"role": "user", "content": "Hello!"}],
      )
  except AuthenticationError as e:
      print(f"Check your API key. request_id={e.request_id}")
  except PermissionDeniedError as e:
      print(f"Not allowed: {e.message} (code={e.code})")
  except RateLimitError as e:
      print(f"Rate limited, retry after {e.retry_after_seconds}s")
  except NotFoundError as e:
      print(f"Not found: {e.message} (param={e.param})")
  except BadRequestError as e:
      print(f"Bad request: {e.message} (param={e.param})")
  except ConflictError as e:
      print(f"Conflict: {e.message} (code={e.code})")
  except InternalServerError as e:
      print(f"Server error. request_id={e.request_id}")
  except APIStatusError as e:
      print(f"Upstream error ({e.status_code}): {e.message}")
  except APIConnectionError as e:
      print(f"Network error: {e.message}")
  except AurikoAPIError as e:
      print(f"API error ({e.status_code}): {e.message}")
  ```

  ```typescript TypeScript Auriko theme={null}
  import {
    Client,
    AurikoAPIError,
    APIConnectionError,
    AuthenticationError,
    PermissionDeniedError,
    BadRequestError,
    ConflictError,
    NotFoundError,
    RateLimitError,
    InternalServerError,
    APIStatusError,
  } from "@auriko/sdk";

  const client = new Client({ apiKey: process.env.AURIKO_API_KEY });

  try {
    const response = await client.chat.completions.create({
      model: "gpt-4o",
      messages: [{ role: "user", content: "Hello!" }],
    });
  } catch (e) {
    if (e instanceof AuthenticationError) {
      console.log(`Check your API key. requestId=${e.requestId}`);
    } else if (e instanceof PermissionDeniedError) {
      console.log(`Not allowed: ${e.message} (code=${e.code})`);
    } else if (e instanceof RateLimitError) {
      console.log(`Rate limited, retry after ${e.retryAfterSeconds}s`);
    } else if (e instanceof NotFoundError) {
      console.log(`Not found: ${e.message} (param=${e.param})`);
    } else if (e instanceof BadRequestError) {
      console.log(`Bad request: ${e.message} (param=${e.param})`);
    } else if (e instanceof ConflictError) {
      console.log(`Conflict: ${e.message} (code=${e.code})`);
    } else if (e instanceof InternalServerError) {
      console.log(`Server error. requestId=${e.requestId}`);
    } else if (e instanceof APIStatusError) {
      console.log(`Upstream error (${e.statusCode}): ${e.message}`);
    } else if (e instanceof APIConnectionError) {
      console.log(`Network error: ${e.message}`);
    } else if (e instanceof AurikoAPIError) {
      console.log(`API error (${e.statusCode}): ${e.message}`);
    }
  }
  ```
</CodeGroup>

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

<CodeGroup>
  ```python Python OpenAI theme={null}
  import os
  from openai import OpenAI

  client = OpenAI(
      api_key=os.environ["AURIKO_API_KEY"],
      base_url="https://api.auriko.ai/v1",
      max_retries=2,
  )

  # Disable retries
  client = OpenAI(
      api_key=os.environ["AURIKO_API_KEY"],
      base_url="https://api.auriko.ai/v1",
      max_retries=0,
  )
  ```

  ```typescript TypeScript OpenAI theme={null}
  import OpenAI from "openai";

  const client = new OpenAI({
      apiKey: process.env.AURIKO_API_KEY,
      baseURL: "https://api.auriko.ai/v1",
      maxRetries: 2,
  });

  // Disable retries
  const noRetry = new OpenAI({
      apiKey: process.env.AURIKO_API_KEY,
      baseURL: "https://api.auriko.ai/v1",
      maxRetries: 0,
  });
  ```

  ```python Python Auriko theme={null}
  import os
  from auriko import Client

  client = Client(
      api_key=os.environ["AURIKO_API_KEY"],
      max_retries=2,
  )

  client = Client(
      api_key=os.environ["AURIKO_API_KEY"],
      max_retries=0,
  )
  ```

  ```typescript TypeScript Auriko theme={null}
  import { Client } from "@auriko/sdk";

  const client = new Client({
      apiKey: process.env.AURIKO_API_KEY,
      maxRetries: 2,
  });

  // Disable retries
  const noRetry = new Client({
      apiKey: process.env.AURIKO_API_KEY,
      maxRetries: 0,
  });
  ```
</CodeGroup>

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