# Errors

> The error envelope and every machine code with its HTTP status, what it means, and what your client should do about it.

Every error is JSON with the same two-field envelope: a stable machine code for your code to branch on, and a human sentence for logs and messages.

```json
{
  "error": "insufficient_credits",
  "detail": "This generation costs 35 credits but your balance is 12."
}
```

`error` values are stable identifiers — match on them, not on `detail`, which can change wording at any time.

```bash
curl -s https://api.figgshield.ai/api/generations/create/ \
  -X POST \
  -H "Authorization: Bearer $FIGGSHIELD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "model": "kling-video", "prompt": "..." }' | jq -r .error
```

## Machine codes

| Code | HTTP | Meaning | What to do |
| --- | --- | --- | --- |
| `validation_error` | 400 | The request body is malformed or a value is out of range — bad params for the model, a top-up under $50, a key name over 64 characters. | Fix the request. `detail` names the offending field. Do not retry unchanged. |
| `invalid_api_key` | 401 | The API key is unknown or revoked, or the `Authorization`/`X-API-Key` header is malformed. | Check the key was copied in full (`figg_` + 40 hex). If revoked, create a new key in Account → API keys. Do not retry with the same key. |
| `insufficient_credits` | 402 | Your balance cannot cover the generation's cost — including the case where an automatic top-up was attempted and its payment failed. | [Top up](https://figgshield.ai/documentation/billing.md), enable auto top-ups, or wait for the next monthly grant. |
| `subscription_required` | 402 | The account has no active subscription — required for generations and top-ups. | Subscribe via `POST /api/billing/checkout/` or in the app, then retry. |
| `admin_required` | 403 | A non-staff account called a staff-only endpoint such as [`GET /api/admin/metrics/`](https://figgshield.ai/documentation/admin.md). | Not retryable — the endpoint requires a staff account. |
| `not_found` | 404 | No such resource, or it belongs to another account — objects are owner-scoped, so someone else's generation UUID also returns this. | Check the UUID and the account the key belongs to. |
| `parallel_limit_reached` | 409 | As many generations already in flight as your plan allows. | Poll until one finishes, then retry. `jobs` in the [usage summary](https://figgshield.ai/documentation/usage.md) shows in-flight versus limit. |
| `rate_limited` | 429 | Too many requests. | Back off exponentially and retry. Polling generation detail more often than every 2 seconds serves no purpose anyway. |
| `model_unavailable` | 503 | The requested model (Kling or Nano Banana 2) is offline or its provider is not responding. | Check [`GET /api/models/`](https://figgshield.ai/documentation/models.md) for `status`; retry later. Nothing was charged. |

Over MCP, the same conditions surface as **tool errors** — for example a `generate_video` call returns an `insufficient_credits` or `subscription_required` error rather than an HTTP status.

## Handling guidance

- **4xx except 409/429** — a client-side problem; retrying the identical request will fail identically. Fix the request, the key, the balance or the subscription first.
- **409 and 429** — transient by design; retry after a delay (poll for a free slot on 409, exponential backoff on 429).
- **503 `model_unavailable`** — transient on the provider side; no charge was made. Retry later.
- **Failed generations are not errors at request time.** A job that is accepted (`201`) but later ends `failed` reports its reason in `error_message` on the generation object, and its credits are refunded automatically.
