How responses and errors are shaped, and what the limits are.
A successful response has a data member (the operation’s object, or an array for a search) and a meta member with the request ID, paging fields (limit, total, next_cursor, and total_relation: lower_bound when a total is a minimum), warnings and the sources behind the answer, each with its own date. An error has an error member with a code, a message written for a person and bounded details for your code, plus the same meta:
{ "error": { "code": "rate_limit_exceeded", "message": "The Free plan allows 30 requests a minute.", "details": { "bucket": "free_per_minute" } }, "meta": { "request_id": "req_3f9a1c7e5b2d4a6f8c0e1b3d7a9c2e4f" }}Optional fields are left out when empty. A few fields return null for unknown, as their descriptions say. Empty results describe the current page and filters. Read meta.warnings for the search scope and check next_cursor for more pages.
| Status | Meaning |
|---|---|
| 200 | A successful lookup, search, resolution or comparison. |
| 400 | Malformed JSON, an invalid parameter combination, an unsupported field, or a valid request too broad to answer. |
| 401 | A missing or invalid API key. |
| 403 | A valid key that does not currently have access. |
| 404 | A valid exact lookup key with no record in the current data. |
| 405 | A method the route does not accept. |
| 409 | An idempotency key conflict, or an ambiguous exact lookup. |
| 413 | A body or batch over its limit. |
| 415 | A POST body that is not application/json. |
| 422 | A code the operation can read but does not support, such as a barcode with no NDC inside. |
| 429 | A limit was reached; Retry-After says when to try again. |
| 500 | An unexpected failure; quote X-Request-ID to support. |
| 503 | A dataset is unavailable, the server is busy, or a request hit a time or work limit (query_timeout). |
| Code | Meaning |
|---|---|
| invalid_request | The body or parameter combination is malformed. |
| unknown_identifier | A valid exact key with no record in the current data. |
| ambiguous_identifier | More than one interpretation or record remains, so nothing is chosen for you. |
| unsupported_barcode | Readable scanner text in a form the API does not read; details.reason names it. |
| invalid_cursor | The cursor is malformed, expired or belongs to another search. |
| batch_too_large | A batch over the operation’s item limit. |
| request_too_complex | A request over the structure limits below. |
| query_too_broad | A valid request that would match too much to answer; details.field names the input to narrow. |
| query_timeout | The request exceeded a time or work limit; narrow it before you send it again. details.constraint names the limit when provided. |
| response_too_large | The response would exceed 8 MiB; lower limit, drop an include or page. |
| unauthorized | A valid API key is required. |
| access_denied | The key is valid but does not currently have access. |
| invalid_idempotency_key | The Idempotency-Key header does not fit the format below. |
| idempotency_conflict | The idempotency key was already used for a different request. |
| idempotency_replay_limit | The idempotency key has reached its attempt limit; use a new key. |
| idempotency_response_changed | A retry with the same idempotency key got a different response than the first; send it as a new request. |
| request_in_progress | The request with this idempotency key is still running; Retry-After says when to ask again. |
| rate_limit_exceeded | A per-minute limit was reached (Free: 30 requests a minute per workspace). |
| quota_exhausted | The daily or monthly allowance was reached; details carries both windows and their reset times. |
| protective_rate_limit_exceeded | A short-window protective limit was reached; it does not count against the allowance. |
| timeout_throttled | Five timeouts from the workspace within ten minutes; refused for ten minutes after the latest. |
| concurrency_limit_exceeded | More requests in flight than the plan allows; Retry-After says when to try again. |
| dataset_unavailable | A dataset the operation needs is not available; details.retry says whether waiting helps. |
| gateway_misconfigured, origin_unavailable and other 503 codes | The API is temporarily unavailable; retry after Retry-After. |
| not_found | The path is not an API route. |
| method_not_allowed | Only GET and JSON POST are accepted. |
| unsupported_media_type | A POST body must be application/json. |
| server_overloaded | The server is at its capacity limit; Retry-After says when to try again. |
| internal_error | An unexpected failure; quote X-Request-ID when you contact support. |
The plans set the allowances and say what counts as a request. A CODE-RESOLVE or INVENTORY-CHECK batch of up to 100 items counts as one request. Additional request limits also apply; a 429 identifies the exceeded limit in details.bucket:
| Bucket | Limit | Code |
|---|---|---|
| daily_quota, monthly_quota | The plan’s daily and monthly allowance | quota_exhausted |
| free_per_minute | Free: 30 requests a minute per workspace | rate_limit_exceeded |
| workspace_protective | 600 requests a minute per workspace | protective_rate_limit_exceeded |
| network_protective | 120 attempts a minute per client address | protective_rate_limit_exceeded |
| repeated_timeouts | 5 timeouts in 10 minutes; refused for 10 minutes after the latest | timeout_throttled |
| workspace_concurrency | Free 1, Builder 1, Starter 2, Business 3 requests in flight per workspace | concurrency_limit_exceeded |
API Playground requests count like any other, and the playground sends at most 20 a minute per network.
A counted response, success or error, carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds until reset) for the allowance window with the least remaining. A request refused before the key is known (401, 403) carries none, and neither does a replayed idempotent response. On a 429 the headers describe the limit that was exceeded and RateLimit-Reset equals Retry-After; the in-flight limit sends RateLimit-Limit, RateLimit-Remaining (0) and Retry-After, with no RateLimit-Reset.
Every operation is read-only, so a retry is safe. To keep a retry from counting twice, send an Idempotency-Key header of 8 to 128 characters (letters, digits and . _ : / + -, starting with a letter or digit). For 24 hours the key is bound to your workspace and the exact method, target and body; a retry with the same key reruns the request and is not counted again when its response matches the first. Reusing a key for different work is refused. If the response changed, the retry answers 409 idempotency_response_changed. A key allows about 10 attempts in total, retries included; after that the API answers 409 idempotency_replay_limit and the client should use a new key.
query_timeout, 503). Lower limit or add a filter.response_too_large, 413). Lower limit, drop an include, or page.batch_too_large, 413).request_too_complex, 413).Every path starts with /v1, and each successful response carries an X-API-Version header. X-Data-As-Of dates the data behind a response (the oldest data_as_of among its sources) and X-Data-Snapshot identifies its release; the API version is the one in the path.
These changes are breaking, and we email at least 90 days’ notice before making one, except as section 8 of the Terms allows:
These are not breaking, and take effect when they appear in these docs:
Section 8 of the Terms of Service sets the notice periods.