## Error Response Format

Error responses are **not** uniformly shaped — the shape depends on which layer rejects the request.

### Rejected before reaching the app

Auth failures and insufficient-balance pre-checks return a nested shape:

```json
{ "error": { "message": "Authentication failed", "type": "invalid_api_key" } }
```

HTTP 401.

```json
{
  "error": {
    "message": "Insufficient balance",
    "type": "insufficient_balance",
    "code": "insufficient_balance"
  },
  "balance": { "available": 0, "required": 10000 }
}
```

HTTP 402. This pre-check uses a rough flat cost estimate, not the exact per-request price — it can pass
and still have the real balance check fail later (surfacing as a 500, see below).

### Rejected by the app

Validation/lookup failures once the request reaches the app return a flat string, not a nested object:

```json
{ "error": "type, prompt, and model are required" }
```

HTTP 400. Similarly, `{ "error": "Unauthorized" }` for 401, and `{ "error": "<message>" }` for 404.

## Common Errors

### 400 — Missing Required Field

```json
{ "error": "type, prompt, and model are required" }
```

### 400 — Unknown Model or Type

```json
{ "error": "Unknown model/type: unknown/model/TEXT_TO_IMAGE" }
```

**Fix:** check the [Models Catalog](/docs/api-reference/generation-models) and [Generation Types](/docs/api-reference/generation-types) for supported models and type combinations.

### 400 — Prompt Too Long

```json
{ "error": "Prompt is too long (max 5000 characters)" }
```

### 402 — Insufficient Balance

```json
{
  "error": { "message": "Insufficient balance", "type": "insufficient_balance", "code": "insufficient_balance" },
  "balance": { "available": 0, "required": 10000 }
}
```

**Fix:** top up your balance from your [dashboard](https://modelstack.cc/dashboard/billing-usage).

## Known edge-case behavior

These aren't documented as a spec — they're real quirks worth handling defensively today:

- `GET /v1/generations/:id` for a nonexistent id returns HTTP 200 with `{ "data": null }`, not a 404.
- `DELETE /v1/generations/:id` on a pending/processing generation returns HTTP 500, not a 400 — only
  completed/failed generations can be deleted.
- There is no enforced rate limit on `/v1/generations*` today — a 429 is not currently reachable for this
  endpoint family.
- Unexpected failures (including the real balance check failing after the pre-check estimate passed)
  return `{ "error": "Internal server error" }` (500) rather than a specific 4xx.

## Error Handling Best Practices

1. **Check the `status` field** when polling — don't assume completion.
2. **Handle 402 gracefully** — notify users when balance runs low, but also handle a late 500 from the
   real balance check, since the upfront 402 check is an estimate.
3. **Validate model/type/parameter combinations before submitting** — use the
   [Generation Types](/docs/api-reference/generation-types) and
   [Parameters](/docs/api-reference/generation-parameters) pages to discover valid combinations, since the
   API does not validate `modelSettings` values itself.
4. **Treat a 500 on `DELETE`** of a generation as "not deletable yet" if the generation might still be
   pending/processing, given the known edge case above.
