# Errors

> The OpenAI error envelope, every Cairva product code, its HTTP status and what to do about it.

Source: https://cairva.xyz/docs/errors

Gateway errors use the OpenAI error envelope, so OpenAI SDKs raise them as normal API errors. Branch on `code`: it's a stable Cairva product code. The `message` is for people and can change.

```json
{
  "error": {
    "message": "allocation_exhausted: 120 input and 0 output tokens remain",
    "type": "invalid_request_error",
    "code": "allocation_exhausted"
  }
}
```

`type` follows the status: `authentication_error` for 401, `rate_limit_error` for 429, `api_error` for 5xx, and `invalid_request_error` for everything else.

## Codes

| Status | Code | When it happens | What to do |
| --- | --- | --- | --- |
| 400 | `validation_failed` | The body isn't JSON, or a field fails validation. The message names the field | Fix the request. See [Chat completions](https://cairva.xyz/docs/chat-completions#request-body) |
| 401 | `unauthenticated` | The key is missing, malformed, unknown or revoked | Check the key. See [Authentication](https://cairva.xyz/docs/authentication#failures) |
| 402 | `allocation_exhausted` | Not enough input or output allowance remains to admit this request. The message says what's left | Shorten the prompt, wait for running requests to settle, or buy more capacity |
| 402 | `allocation_depleted` | The allocation used its full allowance and closed | Buy more capacity |
| 403 | `scope_missing` | The key doesn't have the scope this endpoint needs | Use a key with `inference` or `usage:read` |
| 403 | `key_unscoped` | The key isn't bound to an allocation | Create a key for an allocation |
| 403 | `model_not_permitted` | `model` isn't the one model this key's allocation covers. The message names it | Use the id from `GET /v1/models` |
| 403 | `allocation_reserved` | The agreement behind the allocation hasn't activated yet | Wait for activation |
| 403 | `allocation_not_started` | The window hasn't opened. The message includes the start time | Retry after the start time |
| 403 | `allocation_expired` | The delivery window has ended | Buy new capacity |
| 403 | `allocation_paused` | The supplier or operations paused the allocation. The message carries the reason when one was given | Read the reason in the workspace |
| 403 | `allocation_terminated` | The allocation was terminated and won't serve more requests | Buy new capacity |
| 429 | `rate_limited` | A requests-per-minute, tokens-per-minute or concurrency limit was hit | Wait for `Retry-After` seconds. See [Rate limits](https://cairva.xyz/docs/rate-limits) |
| 502 | `upstream_error` | The provider didn't complete the request. The hold is released and nothing is charged | Retry with backoff |
| 502 | `route_disabled` | Operations paused the route behind this allocation | Check the [status page](https://cairva.xyz/status) |
| 502 | `route_unconfigured` | The route has no provider credentials configured | Contact Cairva |
| 500 | `internal_error` | Something failed inside Cairva | Retry with backoff. Quote the request id if it persists |

## Retry-After

Every `429` carries a `Retry-After` header in seconds: `60` for the requests-per-minute and tokens-per-minute limits, `2` for the concurrency limit. The OpenAI SDKs read it when they retry.

## Retrying safely

A request that failed never consumes allowance. Its hold is released when Cairva records the failure. Retrying a `502 upstream_error` or a `429` can't double-charge you.

`4xx` codes other than `429` won't succeed on retry without a change: a new key, a different `model`, or a new allocation.

## Errors inside a stream

A failure after a stream has opened arrives as one event, `{"error": {"code": "upstream_error", "message": "..."}}`, and then the stream closes. See [Streaming](https://cairva.xyz/docs/streaming#errors-during-the-stream).
