API error codes
Every error is JSON: {"error": code, "message": text}. Branch on the HTTP status and the error code; the message is for people and can change. Limits are explained on the rate limits page.
HTTP/1.1 429 Too Many Requests
retry-after: 12
x-ratelimit-remaining: 0
content-type: application/json
{"error": "rate_limited", "message": "Free anonymous limit reached; get a free API key for more (GET /v1/plans)"}Input
| Code | Status | Meaning | Fix |
|---|---|---|---|
invalid_<param> | 400 | A query or body field failed validation. The code names the field: invalid_country, invalid_limit, invalid_since, invalid_type, invalid_platform, invalid_q, invalid_ticker, invalid_range, invalid_rollup, invalid_until, invalid_to, invalid_id, invalid_email, invalid_token ... | Read the message (it states the allowed values or format) and the parameter list in the API reference. |
invalid_domain | 400 | The path or query value is not a company website domain. | Send a bare domain such as stripe.com. A full URL works; a company name does not (use /v1/companies/resolve with name and country). |
invalid_domains | 400 | A list of domains is empty or has no valid domain. | Send at least one valid domain. |
too_many_domains | 400 | A batch call (POST /v1/coverage) got more domains than allowed. | Split the list. Without a key a batch takes up to 10 domains; with a key up to 50. |
invalid_companyDomain | 400 | Sign-up: the company website is missing, or it is a free-mail or disposable domain. | Send your company website domain, for example acme.com. |
invalid_input | 400 | The body or request has the wrong shape, or contains NUL bytes. | Check the request body against the schema in the API reference. |
invalid_query | 400 | The combination of parameters does not work together (for example resolve needs exactly one of domain, name+country or id). | Send exactly the parameters the message names. |
invalid_id | 400 | A company, VAT or supplier id has a format the route cannot read. | Use the format in the message, for example a supplier domain (siemens.com) or a register id with its country. |
too_many_rows | 400 | A Supplier Watch upload has more than 2,000 suppliers. | Send the list in several calls of up to 2,000 rows. |
invalid_vat | 400 | GET /v1/companies/verify: the VAT number is not valid in format. | Send the number with its country prefix, for example DE811115368. |
unsupported_country | 400 | GET /v1/companies/verify has no live register check for this country. | Use /v1/companies/resolve for the stored record, or check the coverage page for supported countries. |
invalid_cursor | 400 | The cursor is not the nextCursor of a previous page of the same query. | Pass nextCursor unchanged, with the same filters as the first page. |
invalid_json | 400 | The request body is not valid JSON. | Send valid JSON and the header Content-Type: application/json. |
invalid_body | 400 | The body is JSON but not in the shape the route expects. | Compare it with the request body example in the API reference. |
invalid_path | 400 | The URL has a malformed percent-encoding. | URL-encode path and query values (encodeURIComponent). |
unknown_parameter | 400 | A query parameter the route does not have. Unknown parameters are rejected so a typo cannot silently widen a search. | Remove or rename it; the API reference lists the parameters of each route. |
missing_filter | 400 | The route needs at least one filter (for example ticker or q for the review panel). | Add one of the filters named in the message. |
disposable_email | 400 | Sign-up does not accept disposable inboxes. | Use your work address. |
not_found | 404 | There is no data for this input (unknown company, no pay bands for the week, no such watchlist), or the route does not exist. | Check the value. A company we do not track yet is a normal 404; the message says what is missing. Unknown routes: see /openapi.json. |
method_not_allowed | 405 | The route exists but not for this HTTP method. | Use the method named in the message. |
body_too_large | 413 | The request body is over the size limit. | Send less per request (split the batch). |
Key and plan
| Code | Status | Meaning | Fix |
|---|---|---|---|
key_required | 401 | The call needs an API key: the route is not open to anonymous callers (list, search and cursor calls), or anonymous access is not available from your network (hosting or proxy ranges). | Get a free key at /signup/ and send it as Authorization: Bearer <key>. The message names the rule that applied. |
invalid_key | 401 | The key is unknown, or the Authorization header is not in the form "Bearer <key>". | Send the whole key as "Authorization: Bearer <key>" or "X-API-Key: <key>". Keys start with ak_live_. |
key_revoked | 403 | This key was revoked. | Sign in at /account/ and create a new key, or write to us if you did not revoke it. |
key_expired | 403 | A trial key has expired. | Upgrade the plan on /account/; the same key keeps working after payment. |
plan_required | 403 | The option you asked for is not part of any open plan yet (today: daily job alerts; weekly alerts are free). | Use the option the message names, for example frequency weekly. |
watchlist_limit | 403 | The watchlist would track more companies than your plan allows. | Delete a watchlist, or move to a plan with more tracked companies (extra companies can be added on paid plans). |
competitor_limit | 403 | Competitor Watch: the free month has ended, or you already watch as many competitors as you have bought. | Remove a competitor, or buy Competitor Watch (POST /v1/watch/checkout). |
tender_watch_limit | 403 | You reached the number of saved tender searches allowed per account. | Delete one first (DELETE /v1/watch/tenders/{id}). |
job_watch_limit | 403 | You reached the number of saved job searches allowed per account. | Delete one first (DELETE /v1/watch/jobs/{id}). |
supplier_limit | 403 | Supplier Watch: your plan follows as many suppliers as its tracked-company allowance and the upload added none. | Remove suppliers (DELETE /v1/watch/suppliers/{id}) or move to a bigger plan (GET /v1/plans). |
recall_watch_limit | 403 | You reached the number of watched brands (Recall Watch) allowed per account. | Delete one first (DELETE /v1/watch/recalls/{id}). |
device_watch_limit | 403 | You reached the number of EUDAMED manufacturers (device registration alert) an account can follow. | Stop following one first (DELETE /v1/watch/devices/{id}). |
merger_watch_limit | 403 | You reached the number of Merger Watch rules (companies or topics) your plan keeps. | Remove one first (DELETE /v1/watch/mergers/{id}), or move to the Pro plan (Merger Radar) or Business. |
Limits
| Code | Status | Meaning | Fix |
|---|---|---|---|
rate_limited | 429 | Too many requests in the current window: the per-minute limit of your plan, the anonymous daily limit, or an account action (sign-up, verification) repeated too often. | Wait for the seconds in the Retry-After header, then retry. Back off exponentially on repeated 429s; a key or a higher plan raises the limit. |
too_many_in_flight | 429 | Too many requests from your address are running at the same moment. | Wait for earlier requests to finish; keep at most a few in parallel. Retry-After is 1 second. |
quota_exceeded | 429 | The monthly call quota of your plan is used up. | It resets on the 1st (UTC). Upgrade on /account/ and the same key works at once. |
anon_strict | 503 | Anonymous lookups are limited right now: the daily anonymous budget of this route is mostly used, so only recent cached answers are served. | Send an API key (free at /signup/); keyed callers are not affected. Anonymous callers can retry after the Retry-After time (1 hour). |
Account and billing
| Code | Status | Meaning | Fix |
|---|---|---|---|
signup_closed | 503 | Self-serve sign-up is not open yet. | Use the anonymous API meanwhile, or write to us. |
email_unavailable | 503 | We could not send the sign-in email right now. | Try again in a minute. |
no_account | 409 | The account of this key is closed. | Sign up again with your email. |
no_billing | 409 | There is no billing record yet, so there is no portal to open. | Upgrade first (POST /v1/account/checkout). |
billing_unavailable | 502 / 503 | The payment provider cannot be reached, or this plan or add-on cannot be bought online yet. | Try again in a minute, or write to us. |
invalid_signature | 400 | Webhook signature check failed (payment provider webhook only). | Not used by API customers. |
livemode_mismatch | 400 | Webhook event mode does not match this host (payment provider webhook only). | Not used by API customers. |
Server
| Code | Status | Meaning | Fix |
|---|---|---|---|
unavailable | 500 / 503 | A temporary problem on our side, or the part of the service the route needs is not switched on at this host. | Retry after a few seconds with backoff. If it lasts, check /status/ and write to us. |
query_timeout | 503 | The query took longer than its time budget. | Narrow it: fewer terms, add a country or a date range. Retry-After is 30 seconds. |
Retrying safely
Every read route is idempotent, so retrying is safe. Honour Retry-After when present, otherwise back off 1, 2, 4 seconds.
async function getJson(url: string, key?: string, tries = 4): Promise<unknown> {
for (let i = 0; i < tries; i++) {
const res = await fetch(url, { headers: key ? { Authorization: `Bearer ${key}` } : {} });
if (res.ok) return res.json();
const retryable = res.status === 429 || res.status === 503 || res.status === 500;
if (!retryable || i === tries - 1) throw new Error(`${res.status} ${(await res.json()).error}`);
const wait = Number(res.headers.get('retry-after')) || 2 ** i;
await new Promise((r) => setTimeout(r, wait * 1000));
}
}Questions
Which errors should my code retry?
Retry 429 (wait for Retry-After) and 500 or 503 unavailable and query_timeout (retry with backoff). Do not retry other 4xx answers: the same request fails the same way.
Is a 404 an error?
It means we have no data for that input, for example a company we do not track yet. It is a normal answer; handle it as "no data".
How do I know which parameter was wrong?
The code names it (invalid_country, invalid_limit) and the message states the allowed format or values.