# Errors

> Every status and message the API returns, and which errors are passed through from the model provider unchanged.

## The shape of an error

Errors raised by the AlphaNeural API itself have one shape:

```json
{"error": {"message": "insufficient balance: add funds to continue"}}
```

Errors from the model provider or the gateway are **passed through unchanged**: the same status and the same body the gateway returned, usually the OpenAI shape with `message`, `type`, `param` and `code`. They are never rewritten, so "out of credit at the provider", "rate limited" and "model is down" stay distinguishable.

## Chat completions

`POST /v1/chat/completions`, in the order the checks run:

| Status | Message | Cause |
|---|---|---|
| `401` | `missing API key` | No `Authorization` header. |
| `400` | `could not read request body` | The body could not be read. |
| `400` | `invalid JSON body` | The body is not JSON, or a field has the wrong type. |
| `400` | `model is required` | No `model`. |
| `400` | `stream cannot be combined with escalation: the output must be complete before it can be verified` | Both `stream` and `escalation.ladder` were sent. `alphaneural/auto` accepts `stream`. |
| `503` | `alphaneural/auto is not available on this server` | The operator has switched routing off. Name a model instead. |
| `400` | `alphaneural/auto builds its own ladder: ...` | `escalation.ladder` was sent with `alphaneural/auto`. |
| `400` | `escalation.max_rungs must be between 1 and 5` | |
| `400` | `max_spend must be a positive amount in USD: ...` | `alphaneural/auto` was sent `max_spend` of `0` or less. Omit it for the default. |
| `401` | `unknown or inactive API key` | The key does not exist, is inactive, or is not an API key. |
| `500` | `could not verify API key` | A fault on our side. Nothing was recorded; send the same request again. |
| `402` | `insufficient balance: add funds to continue` | Your balance is zero, and the model is not one of your own deployments. See [Billing](https://app.alphaneural.io/docs/billing). |
| `500` | `could not verify balance` | A fault on our side. Nothing was recorded; send the same request again. |
| `404` | `thread not found` | The thread id belongs to another account, or the thread store could not be reached while loading or creating the thread (a request that sent no id gets this too). Retry once before treating the thread as gone. |
| `400` | `unrecognised message content` | A message's `content` is neither a string nor an array of parts. |
| `500` | `could not record the message` | A message could not be saved. Earlier messages in the same request may have been. |
| `500` | `could not read the thread` | `alphaneural/auto` only: the thread could not be loaded to plan the ladder. |
| `503` | `the model catalogue is unavailable, so alphaneural/auto cannot choose a model; retry, or name a model` | |
| `502` | `could not list the models this key may call` | `alphaneural/auto` only: the gateway did not answer which models the key may call. |
| `400` | `escalation.ladder must contain at least one model` | `escalation` was sent with an empty ladder. |
| `500` | `could not build the upstream request`, `could not run the ladder` | A fault on our side, after your messages were recorded. |
| `502` | `could not reach the model` | The gateway could not be reached. |
| `422` | `type: ladder_exhausted`, `code: budget`, `rejected`, `upstream_errors` or `no_candidates` | With `escalation` or `alphaneural/auto`: no answer was accepted. The body also carries the `escalation` object. See [Routing](https://app.alphaneural.io/docs/routing#when-nothing-is-accepted-422). |
| provider's | provider's | The model call failed at the gateway or the provider. Passed through unchanged. |

Everything before `404` `thread not found` in this table leaves your threads untouched. From `unrecognised message content` onwards, messages you sent may already be in the thread; to retry without duplicating them, see [Threads and memory](https://app.alphaneural.io/docs/threads#when-a-call-fails). Two `500`s happen before anything is recorded and are safe to send again unchanged: `could not verify API key` and `could not verify balance`.

A `role` other than `system`, `user`, `assistant` or `tool` cannot be stored, and fails with `500` `could not record the message`.

A `200` from a routed request always carries an accepted answer: `escalation.accepted` is never `false` on a `200`. The only routing failure is the `422`.

## Threads

`GET /v1/threads` and `GET /v1/threads/{thread_id}`:

| Status | Message | Cause |
|---|---|---|
| `401` | `missing API key`, `unknown or inactive API key` | As above. |
| `400` | `limit must be a non-negative integer` | `limit` is negative or not a number. |
| `404` | `thread not found` | The id does not exist or is not yours. |
| `500` | `could not verify API key`, `could not list threads`, `could not load thread` | A fault on our side. |

## Models

`GET /v1/models`:

| Status | Message | Cause |
|---|---|---|
| `401` | `missing API key`, `unknown or inactive API key` | As above. |
| `404` | `model listing is not enabled` | Listing is switched off on the server. |
| `401`, `400`, `429` | the gateway's, with a `type` of `authentication_error`, `invalid_request_error` or `rate_limit_error` | The gateway refused the key: unknown or blocked there, over its own budget, or rate limited. The same key gets the same answer from a chat completion. |
| `502` | `could not load the model list` | The gateway did not answer. |

## Cost traces

`GET /v1/tasks` and `GET /v1/tasks/{task_id}/trace`:

| Status | Message | Cause |
|---|---|---|
| `401` | `missing API key`, `unknown or inactive API key` | As above. |
| `404` | `cost tracing is not enabled` | Tracing is switched off on the server. |
| `400` | `limit must be a non-negative integer` | `limit` is negative or not a number. |
| `400` | `cursor is not valid: pass next_cursor from the previous page unchanged` | The `cursor` was not one the list issued. |
| `500` | `could not list tasks` | A fault on our side. |

A task with no recorded usage is not an error: it returns `200` with a `note`. See [Cost traces](https://app.alphaneural.io/docs/traces#empty-tasks).

## What to do about it

| Status | Do |
|---|---|
| `400` | Fix the request. Retrying unchanged gets the same answer. |
| `404` | Fix the request, unless it is `thread not found` for a thread you know is yours: that can be the thread store failing, so retry once. |
| `422` | The ladder was exhausted. Read `error.code` and `escalation`: raise `max_spend`, change the ladder, or fix the schema, then retry with the same thread id and an empty `messages` array. See [Routing](https://app.alphaneural.io/docs/routing#when-nothing-is-accepted-422). |
| `401` | Check the key on the [API Keys page](https://app.alphaneural.io/profile/me?tab=api-keys). |
| `402` | [Add funds](https://app.alphaneural.io/profile/me?tab=wallet-management), then send the same request again. Nothing was recorded. |
| `429` | Rate limited by the gateway or the provider. Retry with exponential backoff. |
| `500`, `502`, `503` | Retry with backoff. |

When a threaded request fails at the model call, with `502` `could not reach the model`, a `422` from routing, or an error passed through from the gateway or provider (a `429` included), your message is already in the thread. Retry with the same thread id and an empty `messages` array, so it is not added twice. After `500` `could not record the message`, `could not build the upstream request` or `could not run the ladder` you cannot tell from the status alone which of your messages were recorded: [read the thread](https://app.alphaneural.io/docs/threads#read-a-thread) and check its last turns before you retry. Only `could not verify API key` and `could not verify balance` are safe to resend unchanged.
