# Errors

> The per-`code` list and the typed `error.details` shapes are in the published OpenAPI schema — browse them in **[ReDoc](https://api.genomicintelligence.ai/redoc)** or fetch [`/v1/openapi.json`](https://api.genomicintelligence.ai/v1/openapi.json). This page covers the envelope, the discriminate-on-`code` rule, the handling strategy, and a few codes whose behavior the schema can't fully express.

Every non-2xx response uses the unified envelope:

```json
{
  "error": {
    "code": "<machine_readable_snake_case>",
    "message": "<human-readable summary>",
    "request_id": "<server-assigned id>",
    "details": "<optional, code-specific>"
  }
}
```

`error.code` is the **authoritative discriminator**. Never parse `error.message`. The same `request_id` appears on the `X-Request-Id` response header and on every server log line for the request — quote it in support tickets.

`error.details` is **typed per code** in the OpenAPI schema (e.g. `validation_failed`, `task_not_supported_by_model`, `model_not_found`, `sync_too_large` each carry a structured payload). Switch on `code` first, then read `details`; codes without a typed shape carry only the human-readable `message` and should be treated as opaque.

## Notable codes

Most codes are self-explanatory from the OpenAPI schema. A few have behavior worth noting:

- **`504` (no envelope):** a sync request past the upstream read timeout is terminated by the edge proxy with the proxy's body, not the unified `{error: {...}}` envelope (see [Limits](./limits.md)). Retry with `Prefer: respond-async`.
- **`404 not_found` on cross-account access:** accessing another caller's job returns `404`, not `403`, to prevent id enumeration.
- **`413 sync_too_large`:** emitted only by the composite `find-genes-and-predict-expression` workflow when submitted **synchronously** above 50,000 bp; resubmit with `Prefer: respond-async`. No `/v1/tasks/{task}/predict` endpoint emits it. See [Limits](./limits.md).
- **`429 too_many_requests`:** comes from three paths — the per-key concurrency cap, the per-key rate cap, or the edge per-IP cap. All emit `Retry-After`; the application paths also emit `RateLimit-*` headers. On the edge path, `error.request_id` is an edge-assigned identifier (32 hex chars) rather than a UUID, but still correlates with the edge access log. See [Limits](./limits.md).

## Handling errors

Three buckets:

1. **Retryable transient** (`429`, `503`): honor `Retry-After`; if absent, exponential backoff capped at ~30 s.
2. **Permanent** (other `4xx`): surface `error.message` to the user; do not retry. `422` messages are usually deterministic and safe to echo verbatim.
3. **Server bug** (`5xx` other than `503`): capture `request_id` and [contact us](../resources/support.md). One retry is fine; tight loops are not.

---

Back to the workflow: [REST API guide](../getting-started.md) · [Tasks](../tasks.md).
