# Errors

The API returns errors as [RFC 7807 problem documents](https://www.rfc-editor.org/rfc/rfc7807) with the content type `application/problem+json`. Every error response has the same shape, every status code maps to a `type` URI you can switch on programmatically, and every problem body is safe to log verbatim — no secrets are ever included.

## The problem document shape



| Field        | Meaning                                                                                |
|--------------|----------------------------------------------------------------------------------------|
| `type`       | A stable URI identifying the error class. **Switch on this**, not on `title`.          |
| `title`      | A short human-readable summary. Suitable for surfacing to operators, not end users.    |
| `status`     | The HTTP status, duplicated for clients that lose it (e.g. through a proxy).           |
| `detail`     | A longer human-readable explanation. Often contains the specific scope or value.       |
| `instance`   | The request path that produced the error.                                              |
| `request_id` | The opaque request ID. **Always include this when contacting support.**                |

Specific error classes may add extra fields (e.g. `429` includes `retry_after`).

## Catalogue

### 400 Bad Request — `invalid_request`

The request was syntactically malformed: invalid JSON, missing required query parameter, bad enum value. Fix the request shape and retry.



### 401 Unauthorized — `unauthenticated`

Authorization header missing, malformed, the key is revoked, or you used a key that doesn't exist. The body never says **why** the auth failed (we don't reveal whether the key existed) — fix the credential and retry.

### 403 Forbidden — `permission_denied`

The key is valid but lacks the scope this endpoint requires. The `detail` field names the missing scope. Edit the key's scopes in the dashboard.

### 404 Not Found — `not_found`

The path is well-formed but the resource doesn't exist (or doesn't exist *for this org* — we return 404 instead of 403 on cross-org lookups to avoid leaking existence).

### 409 Conflict — `conflict`

A precondition for the request was not met. Reserved for future write endpoints; v1 read endpoints rarely emit this.

### 422 Unprocessable Entity — `validation_failed`

The request was well-formed but a value failed business validation — e.g. a cursor that decodes to an out-of-range offset. Fix the value and retry.

### 429 Too Many Requests — `rate_limit`

You've exceeded the per-org rate limit. Honour `Retry-After`. See [Rate Limiting](/api-reference/rate-limiting).



### 500 Internal Server Error — `internal_error`

Something went wrong on our side. The response will include a `request_id` — include it when you contact support. **Retry with backoff** — most 500s are transient (a transient downstream stutter, a brief connection blip). If you see a sustained 500 rate, treat the API as down.

### 503 Service Unavailable — `service_unavailable`

The API is temporarily degraded — a downstream dependency is briefly unavailable. The response carries `Retry-After`. Back off and retry.

## What clients should do

A robust client implements the following matrix:

| Status | Retry?                  | Strategy                                              |
|--------|-------------------------|-------------------------------------------------------|
| 4xx (except 429) | **No**         | Fix the request and try once more.                    |
| 429    | **Yes**                 | Sleep at least `Retry-After` seconds. Add jitter.     |
| 500    | **Yes** (transient)     | Exponential backoff (e.g. 250ms, 500ms, 1s, 2s).      |
| 503    | **Yes**                 | Sleep `Retry-After`; treat sustained as outage.       |

A safe retry budget is 3–4 attempts with exponential backoff + jitter, capped at ~30 seconds total wait. Beyond that, surface the failure to the caller — the API is genuinely down and retrying won't help.


  Every v1 endpoint is read-only and therefore idempotent — retrying is always safe. When write endpoints arrive in a later phase, they will require an explicit `Idempotency-Key` header for safe retries.


## Logging errors

Log the entire problem document plus the `X-Request-ID` response header. Don't strip `detail` or `instance` — they're the most useful fields when debugging later. **Don't log the API key**.

A minimal log entry looks like:



## Programmatic dispatch

Switch on the `type` URI, not `status` and not `title`. `type` is stable across language ports of your client and tracks our internal taxonomy directly.