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

CodeStatusMeaningFix
invalid_<param>400A 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_domain400The 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_domains400A list of domains is empty or has no valid domain.Send at least one valid domain.
too_many_domains400A 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_companyDomain400Sign-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_input400The body or request has the wrong shape, or contains NUL bytes.Check the request body against the schema in the API reference.
invalid_query400The 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_id400A 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_rows400A Supplier Watch upload has more than 2,000 suppliers.Send the list in several calls of up to 2,000 rows.
invalid_vat400GET /v1/companies/verify: the VAT number is not valid in format.Send the number with its country prefix, for example DE811115368.
unsupported_country400GET /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_cursor400The 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_json400The request body is not valid JSON.Send valid JSON and the header Content-Type: application/json.
invalid_body400The body is JSON but not in the shape the route expects.Compare it with the request body example in the API reference.
invalid_path400The URL has a malformed percent-encoding.URL-encode path and query values (encodeURIComponent).
unknown_parameter400A 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_filter400The 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_email400Sign-up does not accept disposable inboxes.Use your work address.
not_found404There 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_allowed405The route exists but not for this HTTP method.Use the method named in the message.
body_too_large413The request body is over the size limit.Send less per request (split the batch).

Key and plan

CodeStatusMeaningFix
key_required401The 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_key401The 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_revoked403This key was revoked.Sign in at /account/ and create a new key, or write to us if you did not revoke it.
key_expired403A trial key has expired.Upgrade the plan on /account/; the same key keeps working after payment.
plan_required403The 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_limit403The 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_limit403Competitor 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_limit403You reached the number of saved tender searches allowed per account.Delete one first (DELETE /v1/watch/tenders/{id}).
job_watch_limit403You reached the number of saved job searches allowed per account.Delete one first (DELETE /v1/watch/jobs/{id}).
supplier_limit403Supplier 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_limit403You reached the number of watched brands (Recall Watch) allowed per account.Delete one first (DELETE /v1/watch/recalls/{id}).
device_watch_limit403You reached the number of EUDAMED manufacturers (device registration alert) an account can follow.Stop following one first (DELETE /v1/watch/devices/{id}).
merger_watch_limit403You 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

CodeStatusMeaningFix
rate_limited429Too 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_flight429Too 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_exceeded429The 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_strict503Anonymous 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

CodeStatusMeaningFix
signup_closed503Self-serve sign-up is not open yet.Use the anonymous API meanwhile, or write to us.
email_unavailable503We could not send the sign-in email right now.Try again in a minute.
no_account409The account of this key is closed.Sign up again with your email.
no_billing409There is no billing record yet, so there is no portal to open.Upgrade first (POST /v1/account/checkout).
billing_unavailable502 / 503The 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_signature400Webhook signature check failed (payment provider webhook only).Not used by API customers.
livemode_mismatch400Webhook event mode does not match this host (payment provider webhook only).Not used by API customers.

Server

CodeStatusMeaningFix
unavailable500 / 503A 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_timeout503The 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.