Skip to content

Errors and rate limits

Errors come in two shapes. Errors from your library use a structured envelope with a stable code. A small set of errors from the API front door (authentication, rate limiting, account state) use a plain message. Handle both.

Every non-2xx response from your library looks like this:

{
"error": {
"code": "LOCKED",
"message": "Lineage 'guid:6F9619FF8B86D011B42D00C04FC964FF' is locked by another user.",
"details": {
"holder": { "id": 7, "name": "<user>", "display_name": "Sam Rigger" },
"lock": { "id": 31, "lineage_id": "guid:6F9619FF8B86D011B42D00C04FC964FF", "active": true }
}
}
}

Switch on code, not on message. Messages are written for people and can change. details is always an object (often empty) and carries machine-readable context where an endpoint documents it.

Requests rejected before they reach your library have a simpler body:

{ "error": "Rate limit exceeded for this key. Back off and retry." }
Status Cause
401 Missing, unknown or revoked key, or the key’s creator left the workspace.
402 The workspace is suspended for billing. Fix billing in the dashboard.
403 A read-scoped key attempted a write, or the key belongs to a different workspace.
404 The workspace has no library provisioned yet.
413 An MCP request body is missing Content-Length or is larger than 1 MB.
429 Rate limit or agent-call ceiling reached. See below.
503 The API or your library is briefly unavailable, or still being prepared. Honor Retry-After when present.

A safe client checks whether error is an object (envelope) or a string (front door).

These are all the stable codes the envelope can carry. Statuses are the usual ones; details keys are listed where the API sets them.

Code Status Meaning
UNAUTHORIZED 401 Missing, invalid or expired credential. Also returned by device sign-in for an unknown or already-used device code.
FORBIDDEN 403 Authenticated but not allowed: admin-only endpoint, not the owner of the thing you are changing, or the workspace is read-only (details.reason: read_only or ingest_blocked).
RATE_LIMITED 429 Too many sign-in attempts. details.retry_after_seconds says when to retry.
SETUP_ALREADY_DONE 409 Not used by hosted workspaces.
DEVICE_PENDING 400 Device sign-in not approved yet; keep polling (details.interval).
DEVICE_SLOW_DOWN 429 Device sign-in polled too fast (details.interval).
DEVICE_EXPIRED 400 The device code expired; start a new sign-in.
DEVICE_DENIED 400 The person denied the device sign-in.
NOT_FOUND 404 Generic not found: unknown route, missing thumbnail or preview, unknown lock, project or preview artifact.
ENTRY_NOT_FOUND 404 No entry with that id. Bulk calls list the missing ids in details.missing.
OBJECT_NOT_FOUND 404 No object (immutable version) with that id.
LINEAGE_NOT_FOUND 404 No lineage with that id in the catalog.
PACK_NOT_FOUND 404 No pack with that id.
TAG_NOT_FOUND 404 No tag with that id.
COMMENT_NOT_FOUND 404 No comment with that id.
ANNOTATION_NOT_FOUND 404 No annotation with that id.
COLLECTION_NOT_FOUND 404 No collection with that id.
JOB_NOT_FOUND 404 No pull job with that id.
INGEST_NOT_FOUND 404 Reserved for server-side pack ingestion.
USER_NOT_FOUND 404 No member with that id (reviewers, assignees, member profiles).
TOKEN_NOT_FOUND 404 Not used by hosted workspaces.
WEBHOOK_NOT_FOUND 404 No webhook with that id.
REVIEW_REQUEST_NOT_FOUND 404 No review request with that id.
VALIDATION 422 The request is malformed or breaks a rule: bad field, bad cursor, too many items, invalid media. details.errors lists field failures for body and query validation.
CONFLICT 409 Uniqueness or state conflict: duplicate name (details.existing_id for tags), stale face (details.current_object_id), delete blocked because the entry was pulled, an open review round exists (details.open_request_id).
LOCKED 409 Another person holds the check-out lock (details.holder, details.lock).
DENYLISTED 403 The blob’s hash is on the workspace denylist.
HASH_MISMATCH 409 Uploaded bytes do not match the declared hash.
BLOB_MISSING 409 or 404 A commit references bytes that were never uploaded (details.missing, 409), or a download asks for an unknown hash (404).
LINEAGE_CONFLICT 409 The object belongs to a different lineage than the entry (restore, pinned pull, version-pinned comment or annotation).
PROBABLE_LOST_STAMP none Never returned as an error. Surfaces as probable_lost_stamp: true on push items.
STALE_FACE 409 A push would replace a current version you did not observe (details: lineage_id, expected_face_object_id, actual_face_object_id, content_hash). Re-sync before pushing again.
ENGINE_TOO_OLD 409 A pull job’s closure contains assets saved by a newer engine than the claimant (details.objects).
JOB_STATE 409 Illegal pull-job transition, lost claim, or stale claim token (details.status).
DRY_RUN_REQUIRED 409 An admin maintenance sweep was called without a recent dry run.
UPGRADE_REQUIRED 426 The request declared a different API major version.
UNAVAILABLE 503 The library is temporarily unable to take the request (for example details.reason: "policy_unavailable" right after it starts). Retry.
INTERNAL 500 Unexpected error. Details stay in server logs; retry once, then contact support with the time of the request.
EMBEDDINGS_UNAVAILABLE 503 or 409 Visual search is temporarily unavailable (503), or the entry you asked for similar props to has no image embedding yet (409, details.entry_id).

Device sign-in can also return INVALID_REQUEST (400) when device_code is missing.

The API front door limits each API key over a sliding one-minute window:

Traffic Limit per key
Most requests 240 per minute
Upload and media processing: /push/*, /blobs/*, POST /entries/media/presign and /finalize, and /entries/{id}/preview, /thumbnail and /metadata 2,400 per minute

The two budgets are separate, so a large upload does not starve interactive calls made with the same key. Over the limit you get:

HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json; charset=utf-8
{"error": "Rate limit exceeded for this key. Back off and retry."}

Signing in is limited separately: see device sign-in.

REST API calls never count toward a plan allowance. Search, browsing, downloads, thumbnails, uploads and every other REST request are uncounted (they are still rate limited).

The only counted requests are MCP tool calls (tools/call on /mcp), which count against the workspace’s monthly agent-call allowance. When that allowance is used up, MCP tool calls return an AGENT_CALL_LIMIT tool error (a JSON-RPC batch gets a plain 429 with used and limit in the body); searching from the apps and the REST API is unaffected. See MCP errors and limits. See Plans and limits.

Response Retry?
429 Yes, after Retry-After seconds (or details.retry_after_seconds). Add jitter if you run several workers.
503 Yes, after Retry-After when present, otherwise with exponential backoff starting around 2 seconds.
500 INTERNAL Once, then stop and report.
409 codes No. The state changed; read it again and decide. JOB_STATE on a pull job means your claim is gone.
4xx otherwise No. Fix the request.

Uploads and pushes are designed to be retried safely: blob uploads are idempotent by hash, and a commit either registers the whole batch or nothing. See Push (uploads).