> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.milliseconds.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.milliseconds.ai/_mcp/server.

# Errors

## The error envelope

Every JSON error uses one shape. The HTTP status carries the class. The `code` carries the reason.

```json
{ "error": { "code": "string", "message": "string" } }
```

The envelope is the same on every model endpoint: the eight `decision-machine-1` capabilities, the OpenAI-compatible endpoints and `rakeaudio`.

> **Note**
>
> A wrong method or an unrouted path returns `404` with the code `unknown_route`. A `GET` on a model path hits this case. The API checks the key first, so a request without a key gets `401` instead.

## Codes

| Code                         | Status             | Cause                                                                                                                                                                                |
| ---------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `invalid_request`            | 400                | The body failed validation. The facade also raises it for a chat request with no user text, and for a `tool_choice` that names a tool outside `tools`.                               |
| `invalid_schema`             | 400                | An `extract` schema is not an object with `properties`.                                                                                                                              |
| `invalid_image`              | 400                | The `image` field is not decodable base64 of a JPEG, PNG or WebP. A URL value raises it too: the API never fetches an image.                                                         |
| `image_too_large`            | 400                | The image is over 5 MB decoded. The check runs on the base64 length, before any decode.                                                                                              |
| `image_with_texts`           | 400                | The body carries `image` and `texts`. Send `image` alone, or with `text`.                                                                                                            |
| `image_schema_too_large`     | 400                | An `extract` schema on an image holds more than 5 fields. The message reads `extract on an image accepts at most 5 fields. Split the schema across calls, or send the text instead.` |
| `invalid_audio`              | 400                | `rakeaudio` only. The body is empty, or the API cannot decode it as audio.                                                                                                           |
| `audio_too_long`             | 400                | `rakeaudio` only. The audio is longer than 60 minutes.                                                                                                                               |
| `unsupported_media_type`     | 415                | `rakeaudio` only. The `Content-Type` is not `audio/*` or `application/octet-stream`.                                                                                                 |
| `model_unavailable`          | 503                | The model is not available. Retry later.                                                                                                                                             |
| `unsupported_request`        | 400                | A chat request the model does not serve: plain chat, `response_format` of `text` or `json_object`, or `tool_choice: "none"` without a usable `response_format`.                      |
| `missing_api_key`            | 401                | The request carries no `Authorization: Bearer` header. The message reads `Send your key as Authorization: Bearer test_sk-...`.                                                       |
| `invalid_api_key`            | 401                | The key is malformed, unknown or revoked. The message reads `Incorrect API key provided.`                                                                                            |
| `production_access_disabled` | 403                | Production access is unavailable. Open Billing to refresh your status; buy a bundle of at least \$10 if you have no qualifying purchase.                                             |
| `unknown_route`              | 404                | The path or the method is wrong. The message names the valid model paths.                                                                                                            |
| `model_not_found`            | 404                | A wrong model id on `POST /v1/chat/completions`, or a wrong id on `GET /v1/models/{id}`.                                                                                             |
| `rate_limit_exceeded`        | 429                | The organization passed its request or input-token limit. The response carries `retry-after` in seconds.                                                                             |
| `insufficient_quota`         | 429                | The key's credit pool is exhausted. Wait for the monthly test renewal, or add purchased credits for production keys.                                                                 |
| `runner_error`               | 502                | Inference failed on one slot, then failed the retry on a second slot. The message reads `Inference failed twice. Retry.`                                                             |
| `overloaded`                 | 529                | No inference slot became free inside the wait window. The message reads `Every inference slot stayed busy. Retry with backoff.`                                                      |
| `http_error`                 | from the exception | An internal HTTP exception reached the error handler.                                                                                                                                |
| `internal_error`             | 500                | Anything else. The message reads `Internal Server Error`.                                                                                                                            |

The spec declares `502` and `529` on the eight capability endpoints. It declares `529`, but not `502`, on `POST /v1/chat/completions`.

## Validation messages

`invalid_request` reports the first three validation issues. Each issue reads `path: message`. The issues join with `; `, and the path is `body` when the rule covers the whole object.

#### Too few labels

```json
{"error":{"code":"invalid_request","message":"labels: Too small: expected array to have >=2 items"}}
```

#### Text over the limit

```json
{"error":{"code":"invalid_request","message":"text: Too big: expected string to have <=20000 characters"}}
```

#### A tree level with one label

```json
{"error":{"code":"invalid_request","message":"tree.billing.labels: each level needs 2 to 64 labels"}}
```

#### Both text and texts

```json
{"error":{"code":"invalid_request","message":"body: provide text, texts or image"}}
```

> **Warning**
>
> The message `body: provide text, texts or image` also appears when you send **none** of the three fields. All eight capabilities need exactly one of `text`, `texts` or `image`.

## OpenAI-compatible errors

The facade rejects requests it cannot map to a capability.

```json
{"error":{"code":"unsupported_request","message":"decision-machine-1 decides, it does not chat. Send response_format.json_schema for structured extraction, tools for function calling, or use the capability endpoints under /v1/decision-machine-1/."}}
```

```json
{"error":{"code":"model_not_found","message":"Unknown model \"gpt-4o\". Use \"decision-machine-1\"."}}
```

A wrong id on `GET /v1/models/{id}` returns a shorter message.

```json
{"error":{"code":"model_not_found","message":"Unknown model. Use \"decision-machine-1\"."}}
```

## Which errors you retry

| Status | Retry?                         | Action                                                                                                                                             |
| ------ | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | No                             | Fix the request. The same body fails again.                                                                                                        |
| 401    | No                             | Fix the key, or create a new one in the [console](https://console.milliseconds.ai/). See [Authentication](/authentication).                        |
| 403    | No                             | Refresh your status in [Billing](https://console.milliseconds.ai/billing). Buy a qualifying credit bundle only if you have no qualifying purchase. |
| 404    | No                             | Fix the model id, the path, or the method.                                                                                                         |
| 429    | Only for `rate_limit_exceeded` | Wait for `retry-after`. Do not retry `insufficient_quota` until the applicable credit balance changes.                                             |
| 500    | No                             | Do not loop. Capture the message and the request body, then report them.                                                                           |
| 502    | Yes, once or twice             | The service already retried on a second slot. Wait, then send the call again.                                                                      |
| 529    | Yes, with backoff              | Every slot stayed busy. Back off and send the call again.                                                                                          |

> **Note**
>
> Only `429 rate_limit_exceeded` carries `retry-after`. A `429 insufficient_quota` requires a credit balance change, not a timed retry.

Use exponential backoff with jitter on 502 and 529. Honour `retry-after` on 429. Cap the attempts at three. Treat 529 as backpressure, not as a fault. Lower your concurrency when you see it often.

## Retry code

The examples below retry 502 and 529 only, and give up after three attempts.

#### curl

```bash
for attempt in 1 2 3; do
  code=$(curl -s -o /tmp/dm.json -w '%{http_code}' \
    -X POST https://api.milliseconds.ai/v1/decision-machine-1/yes-no \
    -H 'Content-Type: application/json' \
    -H 'authorization: Bearer test_sk-...' \
    -d '{"text":"The server returned a 500.","statement":"This reports an outage."}')
  if [ "$code" != "502" ] && [ "$code" != "529" ]; then break; fi
  sleep $((attempt * 2))
done
cat /tmp/dm.json
```

#### Python

```python
import random, time, requests

URL = "https://api.milliseconds.ai/v1/decision-machine-1/yes-no"
body = {"text": "The server returned a 500.", "statement": "This reports an outage."}

for attempt in range(3):
    r = requests.post(URL, headers={"authorization": "Bearer test_sk-..."}, json=body, timeout=30)
    if r.status_code not in (502, 529):
        break
    time.sleep(2 ** attempt + random.random())

if r.status_code >= 400:
    raise RuntimeError(r.json()["error"]["code"])

print(r.json())
```

#### TypeScript

```typescript
const url = "https://api.milliseconds.ai/v1/decision-machine-1/yes-no";
const body = {
  text: "The server returned a 500.",
  statement: "This reports an outage.",
};

let res: Response;
for (let attempt = 0; attempt < 3; attempt++) {
  res = await fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json", authorization: "Bearer test_sk-..." },
    body: JSON.stringify(body),
  });
  if (res.status !== 502 && res.status !== 529) break;
  await new Promise((r) => setTimeout(r, 2 ** attempt * 1000 + Math.random() * 1000));
}

const data = await res!.json();
if (!res!.ok) throw new Error(data.error.code);
console.log(data);
```

## Authentication and rate-limit errors

Both authentication failures return `401`:

```json
{"error":{"code":"missing_api_key","message":"Send your key as Authorization: Bearer test_sk-..."}}
```

```json
{"error":{"code":"invalid_api_key","message":"Incorrect API key provided."}}
```

A rate-limit breach returns `429`. The message names the limit, `requests` or `tokens`:

```json
{"error":{"code":"rate_limit_exceeded","message":"Rate limit reached for tokens on this organization. Retry after 12s."}}
```

> **Tip**
>
> Read `retry-after` on `429 rate_limit_exceeded`. It gives the wait in seconds. [Limits and rate limits](/reference/limits) documents the `x-ratelimit-*` headers that let you stay below the limit.

## Next

* [Authentication](/authentication) covers the key format and the 401 codes.
* [Limits and rate limits](/reference/limits) lists every field limit that raises `invalid_request`.
* [Input](/concepts/input) shows how to shape `text` and `texts` correctly.
* [Extract](/capabilities/extract) documents the schema rules behind `invalid_schema`.