# Errors

## Envelope

Every failed call returns a JSON body with the same shape, and the matching HTTP status.

```json
{
  "error": {
    "code": "model_busy",
    "message": "model sam3 is busy; retry shortly",
    "retryable": true,
    "retryAfterMs": 5000
  }
}
```

| Field | Meaning |
|---|---|
| `code` | A stable, machine-readable code. Branch on `code`, not on `message`. |
| `message` | A human-readable explanation. The wording can change. |
| `retryable` | `true` when repeating the same request unchanged can succeed. |
| `retryAfterMs` | Suggested wait in milliseconds. 0 when there is no hint. |

`429` and `503` responses also carry a `Retry-After` header in whole seconds.

A failed call costs 0 credits. Nothing is charged when a call returns an error.

## Codes

| Status | Code | Retryable | Meaning |
|---|---|---|---|
| 400 | `invalid_request` | No | The request is malformed, a field is out of range, or the image fails the upload checks. |
| 400 | `invalid_image` | No | The image passed the upload checks but could not be decoded. The same handle keeps answering this code. |
| 400 | `unsupported` | No | The request is valid but uses something not available yet, such as `image.url`. |
| 401 | `unauthorized` | No | The API key is missing, malformed, invalid, expired or revoked. |
| 402 | `insufficient_balance` | No | The balance cannot cover the price of the call. |
| 403 | `model_locked` | No | The key's scope or the account's pool does not allow the model. |
| 403 | `email_unverified` | No | The key owner has not verified an email address. |
| 404 | `not_found` | No | The path does not exist. |
| 404 | `image_not_found` | No | The `img_` handle is unknown, expired or evicted. Upload the image again. |
| 409 | `idempotency_conflict` | No | The `Idempotency-Key` was used with a different request body. |
| 409 | `request_in_progress` | No | The first request with this `Idempotency-Key` is still running. |
| 413 | `payload_too_large` | No | The request body or the image exceeds the size limit. |
| 429 | `rate_limited` | Yes | The per-minute limit of the pool was reached (funded accounts: only when the balance cannot pay for the call). |
| 429 | `daily_limit_reached` | No | The daily allowance of the public pool is used up. |
| 500 | `internal_error` | No | An unexpected server error. |
| 502 | `upstream_error` | Yes | The call failed at the processing stage. |
| 502 | `bad_model_output` | No | The model returned an unusable result. |
| 503 | `model_unavailable` | Yes | No GPU node is serving the model right now. During a short loss of contact with the only worker (about a minute), only interactive calls get this error, with `Retry-After: 5`; sync and batch calls wait in the queue until their deadline. |
| 503 | `model_busy` | Yes | The queue of the model is full or the expected wait is too long. |
| 503 | `unavailable` | Yes | A temporary shortage, for example while image processing is busy or image storage is unavailable. |
| 504 | `upstream_timeout` | Yes | The call passed its deadline. |

The constraints that produce `400 invalid_request` are listed per field in the [API reference](https://stack.foldbun.com/docs/api/).

`daily_limit_reached` carries `Retry-After` until the next UTC day, but repeating the same request before then fails again. Add credit to unlock higher limits. See [Rate limits and pools](https://stack.foldbun.com/docs/rate-limits/).

## Retry guidance

- Repeat a call only when `retryable` is `true`.
- Wait at least `retryAfterMs` (or `Retry-After` seconds) before the next attempt. For `model_unavailable` and `model_busy` without a hint, the server suggests 5000 ms.
- Send an `Idempotency-Key` on calls that spend credits, so that a repeated call cannot be charged or run twice.
- On `404 image_not_found`, upload the image again; the call can then be repeated.
- Other 4xx errors need a change to the request or the account before another attempt.

## Request id

Every response, errors included, carries an `X-Request-Id` header: 16 random hexadecimal characters generated by the server. A value sent by the client is ignored. Successful inference responses also carry a call id in the `id` field (`call_<uuid>`). Quote either value when contacting support.

## Idempotency

Send `Idempotency-Key: <1 to 128 printable ASCII characters>` on any `POST` that spends credits.

- Within an account, a key is remembered for 24 hours.
- Repeating a request with the same key and the same body returns the original response, status and body, with the header `Idempotent-Replayed: true`. The call is not charged and not run again.
- The same key with a different body is `409 idempotency_conflict`.
- The same key while the first request is still running is `409 request_in_progress`.
- Only final outcomes are stored: `2xx` and permanent `4xx`. A `402`, `429` or `5xx` frees the key, so the same request can be retried under it.

```bash
curl https://api.stack.foldbun.com/v1/ocr \
  -H "Authorization: Bearer $FOLDBUN_API_KEY" \
  -H "Idempotency-Key: 6d1a8b3e-1f6e-4f0c-9d54-2b8f7c1e0a11" \
  -H "Content-Type: application/json" \
  -d '{"image": {"id": "img_2dedd0716bfd46ec29b286a279d18b6fc80631e5d90fedb5c63eb36f231622e6"}}'
```
