Developers · Errors & limits
Errors, rate limits & pagination
One error shape across every route, a per-key rate limit that fails closed, and cursor-based pagination for every list endpoint.
The envelope
Error shape
{ error: string, fields?: Record<string, string> } — Every error is JSON with a human-readable error message. 422 validation failures add a fields map of field-path → message.
A plain error
{
"error": "Not found"
}A 422 validation failure
{
"error": "Validation failed",
"fields": {
"legalName": "Required",
"lei": "Must be exactly 20 letters or digits"
}
}- 429 responses set Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers.
- Entity 412 revision conflicts add currentRevision to the body and set an ETag of the current revision.
- The webhook-subscription 409 limit error adds limit: 25.
- Datastore failures on the generic resource routes surface as 502 { error: "Bad Gateway" }; entity-command failures surface as 503.
- Restricted records a key cannot access are reported as 404 Not found, indistinguishable from absent records.
Every condition
Status codes
Every distinct status/error/condition combination documented anywhere in the API, collapsed from the per-endpoint lists.
| Status | Error | When |
|---|---|---|
| 400 | Idempotency-Key header is required. | The Idempotency-Key header is absent. |
| 400 | Idempotency-Key header is required. | If-Match must contain one numeric entity revision. | The Idempotency-Key header is absent, or If-Match is present but not a single non-negative integer. |
| 400 | Invalid JSON body. | The request body is not parseable JSON. |
| 400 | Invalid pagination cursor. | The cursor is malformed or was tampered with. |
| 401 | Unauthorized | Missing, malformed, unknown, revoked, or expired API key (no detail is leaked about which). |
| 403 | Forbidden: <reason> | The key lacks the required scope, the workspace subscription is not an active Enterprise plan, workspace access is suspended, or the required module is not on the plan. |
| 403 | Forbidden: manage_webhooks capability required | The key does not carry the manage_webhooks capability. |
| 403 | Forbidden: The restricted_records capability is required to change record access. | restricted or viewerIds supplied without the capability. |
| 403 | Outbound webhooks require Enterprise. | The workspace billing document is not an active/trialing Enterprise subscription. |
| 404 | Not found | No record with this id exists in the key's workspace (restricted records the key cannot access are indistinguishable from absent ones). |
| 404 | Not found | The entity does not exist, has no approved deletion request, or is a restricted record the key cannot access. |
| 409 | A workspace may have at most 25 webhook subscriptions. | The 25-subscription cap is reached. Body carries limit: 25. |
| 409 | Entity conflict | Entity limit reached. | Idempotency key conflicts with an earlier request. | A conflicting entity exists, the free-tier entity cap is reached, or the Idempotency-Key was already used with a different request body. |
| 409 | Webhook subscription is not deliverable. | The stored subscription fails integrity validation (disabled, malformed URL/events/secret, or wrong workspace). |
| 412 | Entity revision does not match. | If-Match does not equal the stored revision. Body carries currentRevision; ETag header carries the current revision. |
| 413 | Request body too large. | The JSON body exceeds 1 MiB (1,048,576 bytes). |
| 422 | Validation failed | The body is not a plain JSON object, contains unknown keys, or a field fails its schema; a fields map names each offending field. |
| 422 | Validation failed | Unknown keys, bad URL, unknown/duplicate events, or recordAccess: restricted without the capability — fields map names each. |
| 422 | Validation failed | Unknown keys, no updatable fields, or a field fails validation. |
| 428 | If-Match header is required. | The If-Match header is absent. |
| 429 | Rate limit exceeded | More than 1,000 requests in the key's 15-minute window. Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset headers are set. |
| 502 | Bad Gateway | The datastore read or write failed. |
| 503 | Entity mutations are temporarily unavailable. | The workspace's entity-mutation mode is transitioning (Retry-After: 5 is set). |
| 503 | Entity state requires reconciliation. | Persisted entity/receipt state failed integrity checks (logged for operators). |
| 503 | Service unavailable | An unclassified server error occurred. |
| 503 | The public API is not configured in this environment. | The deployment has no API_KEY_PEPPER or Firebase Admin credentials (the feature is dormant). |
Per key
Rate limits
1000 requests per 15 minutes, per API key. Per API key (a Firestore-transaction counter per key; in production an unavailable limiter fails CLOSED).
HTTP/1.1 429 Too Many Requests
Retry-After: 42
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1752570600
{
"error": "Rate limit exceeded"
}Every response — success or failure — carries the key’s current standing via Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset.
List endpoints
Pagination
{ data: T[], nextCursor: string | null } — pass ?cursor= from the previous page to fetch the next.
Request
curl "https://app.alethiahq.com/api/v1/entities?limit=50&cursor=eyJvZmZzZXQiOjUwfQ" \
-H "Authorization: Bearer tk_{workspaceId}_{secret}"Response
{
"data": ["...up to 50 records"],
"nextCursor": "eyJvZmZzZXQiOjUwfQ"
}- Default page size 50, maximum 200.
- Collections are ordered by document id (stable, deterministic).
- Pass nextCursor back as ?cursor= to fetch the following page; null means the end.
- Cursors are OPAQUE. For keys without restricted_records they are AES-256-GCM-encrypted tokens; tampering yields 400 "Invalid pagination cursor."
- A page may contain fewer than limit items even when nextCursor is non-null (restricted records are filtered after paging).
- The webhook-subscriptions list is not paginated (capped at 25).
Next
Continue reading
Building something that needs more headroom?
Talk to us about your expected volume — rate limits are per key, and a workspace can hold more than one.