Concepts
Errors
The error envelope, every error code and what to do about each.
Every error from the data API has the same JSON shape, plus the response header x-request-id:
{
"error": {
"code": "invalid_request",
"message": "unknown query parameter: limt",
"request_id": "8f5c0c3e-..."
}
}code is the stable machine code; branch on it, not on message (which is for people and may change). A client must tolerate codes it does not know. The JSON Schema is error. Assert on the code, not just the status: several different problems share a status (two kinds of 429, several kinds of 503).
Codes of the data API#
| Status | error.code | Meaning | What to do |
|---|---|---|---|
| 400 | invalid_request | Unknown or repeated query parameter, value out of range, malformed id/date, a cursor from another route, a wrong combination of parameters. | Fix the request. Do not retry unchanged. |
| 401 | unauthorized | No key, malformed key, unknown key or revoked key. | Check the key and header. |
| 403 | ip_not_allowed | The key has an IP allowlist and the request came from outside it. | Call from an allowed address or edit the allowlist. |
| 403 | forbidden | The account is not allowed to use the key (for example it is disabled). | Contact the account owner. |
| 404 | not_found | The id is well formed but nothing is stored under it (an unknown series, statute, record), or the path has no id. | Check the id. For stored-in-stages datasets the record may not be loaded yet. |
| 410 | taken_down | The judgment has been taken down and is hidden (judgment routes, when the deployment hides takedowns). | Do not retry. |
| 429 | rate_limited | Too many requests from your source IP, checked before the key. | Wait Retry-After seconds, then retry with backoff. |
| 429 | usage_limit_exceeded | The key's quota for its period is spent. | Do not retry. Use another key or wait for the period to roll. |
| 501 | corpus_not_migrated | A judgment route whose corpus has not been loaded yet. | Try later. |
| 503 | store_unavailable | The database behind the route is not reachable or not configured. | Retry with backoff; if it persists, report the request_id. |
| 503 | terms_unread | The source's publisher terms have not been recorded as read, so the route will not serve its data. | Not a transient error. See Freshness and availability. |
| 503 | index_unavailable | The search index behind a retrieval route has no index yet. | Retry later. |
| 503 | retrieval_unavailable | The retrieval service is not reachable. | Retry with backoff. |
| 500 | internal | Something went wrong on our side. | Retry once; if it persists, report the request_id. |
Codes of the accounts API#
The accounts service (the console's backend) uses the same envelope and adds codes for sessions and writes: conflict (409), origin_not_allowed, csrf_required, step_up_required, account_disabled, email_mismatch (403), validation_failed, plus codes for platform-staff features. See the accounts API.
Handling errors well#
- Log
error.codeandrequest_idfor every non-2xx answer. - Retry only what can change:
429 rate_limited(afterRetry-After) and503 store_unavailable/500 internal(with exponential backoff and a cap). Never retry400,401,403,404,429 usage_limit_exceededor503 terms_unreadunchanged. - Treat
meta.status: "empty"on a 200 as a valid, empty answer, not an error.
The legacy MCP routes#
POST /v1/mcp/call keeps the older flat error shape { "error": "message" } so existing tool clients keep working. See MCP compatibility.
Source: content/concepts/errors.md