Skip to main content
Every error the API returns has the same shape, and any write can be made safe to retry. This guide covers what each error means, which ones to retry, and how. The SDKs do most of it for you: see Retries and idempotency.

The error envelope

  • type names the kind of error (see the table below). Branch on it and on code; message is meant for people.
  • details is there when the error is about something specific: each has a machine-readable code, usually the field or parameter it is about, and a message. Some codes add more: usage and limit, current_revision, or the record_id that already has a value.
  • request_id is also in the X-Request-Id header of every response, errors or not. Log it, and include it when you ask for support.

Statuses and types

  • 400: the request is malformed, such as JSON that doesn’t parse, a sort or cursor the API doesn’t know, or a body key the operation doesn’t take (unknown_parameter, naming the key). Fix the request: retrying it unchanged fails the same way.
  • 401: the API key is missing, invalid, revoked or expired.
  • 402: the change would go over a hard limit of your organization’s plan, such as its number of projects, collections, records, members or webhooks, or, on Free, its storage. The detail names the limit in field, with the current usage and the limit. A file larger than the plan allows (10 MB on Free, 100 MB on paid plans) is refused when its upload starts: the field is byte_size, usage is the file’s size and limit the largest upload.
  • 403: the key may see the resource but not do this, for example when it lacks a scope (the message names it; there is no detail code) or is published-only. An expand that embeds what the key can’t read is not_permitted.
  • 404: the resource doesn’t exist, or the key can’t see it, such as another project’s. The two look the same on purpose.
  • 409: the request conflicts with the resource’s state. A deleted resource must be restored first (deleted); a record has one draft at most (draft_exists), and a live record with a pending draft can’t be published until the draft is merged (draft_pending); a collection is being migrated (migration_in_progress). Retrying helps only once the state changes; in_progress is the exception (see below).
  • 422: the values don’t pass validation. For a record, each detail’s field is the field’s key in data; unknown keys in data are unknown_field, and a value another record already has in a unique field is taken, with that record’s record_id.
  • 500 is an unexpected failure in the API, and 503 means a service such as search is briefly unavailable. Retry both with backoff.

Retry safely with Idempotency-Key

A request can fail after the API has done the work, when the response is lost on the way back. Send an Idempotency-Key header on any POST, PUT, PATCH or DELETE, and you can retry it without doing it twice:
  • The key is 1 to 255 printable ASCII characters; a UUID is a good choice. Keys are scoped to the API key that sends them.
  • The first response below 500 is stored for 24 hours, validation errors included. A retry with the same key and the same request (method, path, query string and body bytes) gets that response again, with its original ETag and Location headers and Idempotent-Replayed: true. Nothing runs a second time.
  • The same key with a different request is refused: 400, detail code reused. Send the same bytes when you retry, and use a new key for a new request.
  • A retry while the first request is still running gets 409, detail code in_progress. Wait and retry. A request that hasn’t finished after 5 minutes is treated as abandoned, and the key can be used again.
  • A 5xx response doesn’t keep the key, so retrying runs the request again.
The SDKs send a key with every write, the same on each of their retries. For a job that may run twice, pass your own key, as in the SDK guide.

Rate limits

Every authenticated response carries the current window: An API key’s limit is per minute and depends on its organization’s plan (the pricing page lists it). A published-only key can ship inside an app people run, so it is counted per client IP address: one user can’t use up everyone else’s. Requests with an invalid key are limited per IP address too. Over the limit, the API answers 429 rate_limited with Retry-After, in seconds. The request was neither performed nor counted, so wait that long and send it again (with the same Idempotency-Key, for a write).
A 429 with a plan_limit detail is different: on Free, the organization has used the month’s API requests, and Retry-After runs to the start of the next month. See API requests.

What the SDKs retry

The SDKs retry connection errors, 429, 500, 502, 503, 504 and in_progress for you (Retries and idempotency has the rules). What reaches your code is raised as its type’s class. Change the number of retries when you create the client:

Concurrent edits

Without a check, the last write wins: your update can overwrite a change someone made after you read the record. Both writes are kept in its history, but the record shows the last. To prevent that, use the record’s revision. Record and draft responses carry the revision as an ETag, such as "7". Send it back as If-Match when you change the record:
If someone changed the record since revision 6, nothing is written and the API answers 412:
Reload the record, then merge your change into it or ask the person editing what to keep. Don’t retry the same request: it fails the same way. If-Match works on record writes (a create with upsert_on, which may update, and update, delete, restore, publish, unpublish, revert) and on a draft’s update, discard and merge, where the revision is the draft’s. In the SDKs, pass the record you read as ifMatch (if_match): see Concurrent edits.