# Errors

Every public endpoint reports an error the same way, a JSON object with an error message and an HTTP status. This page lists the statuses the API uses and where each one comes from.

## The format

```json
{ "error": "Block not found" }
```

- **One field**, `error`: a short English sentence meant to be shown to a person. A few errors add a field that helps you recover (a witness cosignature refused as too old also returns `last`, the size it last cosigned).
- **The status code carries the meaning.** Branch on the status, not on the text: messages are reworded from time to time.
- **Never cached.** Error responses carry `Cache-Control: no-store`, so a retry always reaches the server.
- **CORS headers are set on errors too**, so a page on another site can read them.

A path that matches no route isn't an API error: it gets the site's HTML 404 page. Check the `Content-Type` before parsing.

## When a 200 still means no

Some answers are successful requests that say something didn't or wouldn't happen. Check for these in the body:

| Endpoint | What to check |
| --- | --- |
| `POST /api/tx` | The record's `status`. A transaction that was valid enough to sequence but broke a ledger rule is sealed with `status: "failed"` and an `error`, and the call answers 200. See [Submitting](/transactions/submitting). |
| `GET /api/market/quote` | `error` is not `null`: the ledger would refuse that swap (too small, not enough balance, not enough liquidity). See [Market](/network/market#quote). |
| `GET /api/name/{name}` | `available: false` with a `reason`. |
| `GET /api/search` | `{ "to": null }`: nothing matched. |

## Status codes {#codes}

| Status | Meaning | Where it comes from |
| --- | --- | --- |
| 400 | The request is wrong | A path or query value that can't be used: a block height that isn't a whole number, a quote without `side` or with an `amount` that isn't whole base units. A body that isn't JSON (`POST /api/tx` says `"Body must be JSON: { tx }"`). A transaction the sequencer rejects before sequencing it: malformed, too large (this is a 400, not a 413), a wrong chain, a timestamp too far from network time, a wrong nonce, a name that can't be registered, and the other checks on [Submitting](/transactions/submitting). On MLX Node's routes: an unknown job or a result without its output hash. |
| 401 | The request isn't signed, or the signature doesn't check | `POST /api/tx`: a transaction not signed by its wallet's own key (`"This transaction isn't signed by luca.mlx's key."`). MLX Node's routes (`/api/worker/*`, `/api/log/cosign`) need the `x-mlx-device` header: missing, malformed, a bad signature, or a Mac clock too far from the server's. Wallet-signed routes (the MLX.app companion API and the distributor portal) need `x-mlx-auth`: `"Unlock your wallet to continue"`. |
| 403 | Signed, but not allowed | A Mac that isn't linked to a wallet sending a result (`"Link this Mac to your wallet first"`); a witness cosignature whose signature doesn't match; a checkout refusing a request (checkout not open on this network). |
| 404 | Nothing there | A block, transaction, account, key, seat or checkpoint that doesn't exist: `"Block not found"`, `"Transaction not found"`, `"Account not found"`, `"No account uses this key"`, `"Seat not found"`. |
| 409 | It conflicts with what already happened | MLX Node's routes: a signed request sent twice (`"Request already used"`), a result for a job this Mac wasn't handed, a second result for the same job, a result faster than the job can run, a cosignature older than the Mac's last one. |
| 413 | The body is too large | MLX Node's routes and wallet-signed routes refuse bodies over their size cap. |
| 429 | Too many requests | A rate limit. Wait and retry; see [Rate limits](/resources/rate-limits). |
| 500 | Something failed on the server | Anything unexpected while reading or writing the ledger. The message is the underlying error (on `/api/token`, `/api/compute` and `/api/compute/work` cut to a short line; MLX Node's routes just say `"Server error"`). Safe to retry. |
| 503 | A source is down | `GET /api/market/aapl` when market data can't be fetched. |

## Retrying

- **400, 401, 403, 404, 413**: don't retry the same request; fix it first.
- **409**: read the message. A replayed signed request needs a fresh signature; a withdrawal that can't be paid now can be tried again later.
- **429**: wait until the limit's window has passed (most are per minute).
- **500, 503**: retry with a backoff.
- **Transactions** are safe to resend: the ledger keys a transaction by its hash, so sending the same signed transaction again returns the record it already has and changes nothing.
