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

# Errors

> Every error the API returns, and what your client should do.

## Error shape

Every error response uses the same JSON shape:

```json Error theme={null}
{
  "error": {
    "message": "Send 1 to 128 questions.",
    "type": "invalid_request_error",
    "code": "invalid_request",
    "param": "questions"
  }
}
```

* `type` is the broad class and `code` the exact condition. Branch on `code`.
* `param`, when present, is the path of the offending field, such as `model`, `questions.severity.criteria` or `questions.team.criteria.billing`. Question names and option names can appear in it; your `state`, `instructions` and descriptions never do.
* Some errors carry extra fields inside `error`, listed below.
* `message` is for humans and may change. It never contains your request content.

Every response, success or error, has an `x-request-id` header (`req_...`). Include it when you contact support.

**No error is charged.** Only a `200` response is charged. See [Billing](/billing).

## All errors

| Status | `type` | `code` | Extra fields | What to do |
| - | - | - | - | - |
| 400 | `invalid_request_error` | `invalid_request` | `param` | Fix the request field named in `param`. Do not retry unchanged. |
| 400 | `invalid_request_error` | `invalid_request` | | The model rejected the input, most often because the `state` plus a question is over the model's [input-length limit](/limits#input-length). Shorten the `state` or that question; do not retry unchanged. |
| 400 | `invalid_request_error` | `invalid_model` | `param: "model"`, `valid_models` | Use one of `valid_models`. |
| 401 | `authentication_error` | `invalid_api_key` | | Send an active `tcd_` key as `Authorization: Bearer <key>`. Create a new key if it was revoked or expired. |
| 402 | `billing_error` | `insufficient_balance` | `required_usd`, `available_usd`, `topup_url` | Add credit (see [Billing](/billing#adding-credit)), then retry. |
| 402 | `billing_error` | `key_limit_reached` | `limit_usd`, `resets_at` | Wait until `resets_at`, or use another key. If `resets_at` is `null`, the limit is a lifetime limit: create a new key. |
| 410 | `invalid_request_error` | `model_retired` | `replacement_model` | Move to `replacement_model`. If it is `null`, pick another active id from `GET /v1/models`. |
| 413 | `invalid_request_error` | `request_too_large` | `param: "body"` | Shorten the `state` or split the request. |
| 429 | `rate_limit_error` | `rate_limited` | `Retry-After` header | Wait `Retry-After` seconds, then retry. |
| 429 | `rate_limit_error` | `capacity_exceeded` | `Retry-After` header | The model is at capacity. Wait `Retry-After` seconds, then retry. |
| 500 | `api_error` | `internal_error` | | Retry with backoff. |
| 502 | `api_error` | `upstream_error` | | The model returned an invalid answer. Retry with backoff. |
| 503 | `api_error` | `model_unavailable` | | The model is temporarily unavailable. Retry with backoff, or call another model id. |
| 504 | `api_error` | `upstream_timeout` | | The model did not answer in time. Retry with backoff. |

Money fields are decimal strings in USD with six decimals, such as `"0.000150"`. `available_usd` can be negative.

## Order of checks

A request that fails several checks gets only the first error, in this order:

1. Body over the global size limit: `413`.
2. API key: `401`.
3. Request shape and global limits: `400 invalid_request`.
4. Model: unknown `400 invalid_model`, retired `410`, unavailable `503`; then the model's own body limit `413` and limits `400`.
5. Rate limit: `429 rate_limited`.
6. The key's spend limit: `402 key_limit_reached`.
7. Your balance: `402 insufficient_balance`.
8. The model call: `400`, `429 capacity_exceeded`, `502`, `503` or `504`.

Every request that gets past step 2 counts against your rate limit, including requests refused later with `400`, `410`, `413` or `503`.

## Examples

### 400 `invalid_request`

```json Error theme={null}
{
  "error": {
    "message": "A score question needs 1 to 10 levels.",
    "type": "invalid_request_error",
    "code": "invalid_request",
    "param": "questions.severity.criteria"
  }
}
```

A body that is not valid JSON names `param: "body"`. When the model itself rejects the input, the error has no `param`. The most common cause is an input over the model's [input-length limit](/limits#input-length): the API never shortens your input to make it fit. Like every error, this one is not charged.

```json Error theme={null}
{
  "error": {
    "message": "The model rejected the request input.",
    "type": "invalid_request_error",
    "code": "invalid_request"
  }
}
```

### 400 `invalid_model`

```json Error theme={null}
{
  "error": {
    "message": "The requested model does not exist.",
    "type": "invalid_request_error",
    "code": "invalid_model",
    "param": "model",
    "valid_models": ["torchcast-decider-latest", "torchcast-decider-4b-v1-1-api-1"]
  }
}
```

### 401 `invalid_api_key`

```json Error theme={null}
{
  "error": {
    "message": "Invalid API key. The key may be missing, revoked or expired; send an active Decision API key as a bearer token.",
    "type": "authentication_error",
    "code": "invalid_api_key"
  }
}
```

### 402 `insufficient_balance`

Returned when your balance is zero or negative, or is more than 0.01 USD short of the amount the request sets aside. `required_usd` is that amount; see [How a request draws on the balance](/billing#how-a-request-draws-on-the-balance). `topup_url` is `null` while card top-ups in the console are not available; see [Adding credit](/billing#adding-credit). Once they are, it links to the console page for adding credit.

```json Error theme={null}
{
  "error": {
    "message": "Insufficient balance for this request. Add credits to continue.",
    "type": "billing_error",
    "code": "insufficient_balance",
    "required_usd": "0.000150",
    "available_usd": "0.000000",
    "topup_url": null
  }
}
```

### 402 `key_limit_reached`

Returned when the request would take the key past its spend limit. The check counts what the key has been charged in the current period plus the requests it has in flight, so a key can be refused slightly before its charges reach the limit. `resets_at` is the start of the next period, in UTC (for example midnight UTC for a daily limit). For a limit with no reset period it is `null`: the limit covers the key's whole lifetime, and a key's limit cannot be changed after it is created, so create a new key to continue.

```json Error theme={null}
{
  "error": {
    "message": "This API key has reached its spend limit for the current period.",
    "type": "billing_error",
    "code": "key_limit_reached",
    "limit_usd": "5.000000",
    "resets_at": "2026-10-13T00:00:00Z"
  }
}
```

### 410 `model_retired`

```json Error theme={null}
{
  "error": {
    "message": "The requested model has been retired.",
    "type": "invalid_request_error",
    "code": "model_retired",
    "replacement_model": "torchcast-decider-4b-v1-1-api-1"
  }
}
```

### 413 `request_too_large`

```json Error theme={null}
{
  "error": {
    "message": "The request body is too large.",
    "type": "invalid_request_error",
    "code": "request_too_large",
    "param": "body"
  }
}
```

### 429 `rate_limited` and `capacity_exceeded`

Both carry a `Retry-After` header with the number of seconds to wait. `rate_limited` means your key or account is over its rate limit; the `x-ratelimit-*` headers tell you which limit and when it resets (see [Limits](/limits#rate-limits)). `capacity_exceeded` means the model is busy, regardless of your own limit.

```json Error theme={null}
{
  "error": {
    "message": "Rate limit exceeded. Retry after the indicated delay.",
    "type": "rate_limit_error",
    "code": "rate_limited"
  }
}
```

```json Error theme={null}
{
  "error": {
    "message": "The model is at capacity. Retry after the indicated delay.",
    "type": "rate_limit_error",
    "code": "capacity_exceeded"
  }
}
```

### 5xx

```json Error theme={null}
{
  "error": {
    "message": "The requested model is temporarily unavailable.",
    "type": "api_error",
    "code": "model_unavailable"
  }
}
```

`internal_error` (500), `upstream_error` (502) and `upstream_timeout` (504) have the same shape.

## Retrying

* Retry `429`, `500`, `502`, `503` and `504`. For `429`, wait for `Retry-After`; for the others, use exponential backoff with jitter.
* Do not retry `400`, `401`, `402`, `410` or `413` unchanged: the same request fails the same way.
* A failed request is not charged, so retrying it costs nothing extra. A request that succeeded is charged every time you send it; see [Limits](/limits#retries-and-idempotency).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.