Errors
The DiscoLike API uses conventional HTTP response codes to indicate success or failure of requests.
| Error Code | Meaning |
|---|---|
| 400 | Bad Request — The request could not be processed against your data or the input you sent |
| 401 | Unauthorized — Your API key is wrong |
| 403 | Forbidden — You don’t have access to this resource, or the request costs more than the remaining balance (see Insufficient balance) |
| 404 | Not Found — The specified resource could not be found |
| 422 | Unprocessable Entity — The request failed parameter validation |
| 429 | Too Many Requests — You’re making too many requests, or have too much work queued or running (see Rate Limits) |
| 500 | Internal Server Error — We had a problem with our server |
| 503 | Service Unavailable — We’re temporarily offline for maintenance, or a billed request could not be priced (Unable to price this request, try again); retry it |
Insufficient balance
Section titled “Insufficient balance”Every billed request is priced before it bills anything, at your plan’s per-request fee plus its per-1K record rate, as if every record it could return were new to you (records you already received in the last 90 days are still free when the request is billed). A request the remaining balance for the cycle (plan allowance, carryover and top-up credits, less what you have spent) cannot cover is refused with a 403. Most requests are refused before they search or call an LLM. Four lookups with no size you set, /vendors, /publiclink, /subsidiaries and /redirects (and the MCP public-company-links, subsidiaries-and-parent-companies and domain-redirects tools), price the rows they found instead, so they are refused after the lookup but before anything is returned or billed. Accounts without a spending cap are never refused.
The detail of this 403 is an object, not a string:
{ "detail": { "code": "INSUFFICIENT_BALANCE", "message": "This request could cost up to $140.70 but the account has $43.75 left this cycle. Lower it to at most 3075 records, or upgrade or top up to continue.", "remaining_usd": 43.75, "max_affordable_records": 3075, "requested_records": 10000, "upgrade_url": "https://app.discolike.com/account/management/plans" }}| Field | Type | Description |
|---|---|---|
code | string | Always INSUFFICIENT_BALANCE |
message | string | Human-readable explanation, safe to show to a user; when the request has a size you can lower, it names the largest one that fits and its unit |
remaining_usd | number | Balance left in the current cycle |
max_affordable_records | integer | The largest size the same request could ask for and still fit (0 when even the request fee does not fit), in the request’s own unit |
requested_records | integer | The size the request asked for, in the same unit |
upgrade_url | string | Where to upgrade the plan or buy top-up credits |
Both counts are in the unit you size the request by, which the message names: max_records for /discover and GET /contacts (records), max_records or max_companies for POST /contacts/discover (contacts or companies; a page also bills the companies its contacts come from), the uploaded rows for /append, the queries for an enriched contact bulk match, the domains for /contacts/discover/generate (with find_emails, every contact a domain can yield counts too) and the personas for DiscoGen persona research (a persona counts twice when its company is included). Retrying at max_affordable_records fits. A request with no size to lower (a single-company lookup such as /bizdata, /score, /growth, /extract or /match, a single email find, or one of the four row-priced lookups) only asks you to top up.
MCP tools refuse with the same message as a tool error. Asynchronous tasks (bulk match, contact bulk match, segment, DiscoGen, ContaGen, ICP validation, email find) are priced when they are submitted. While they run:
- A long DiscoGen, ContaGen or ICP validation task whose balance runs out stops before the next batch and keeps, and bills, the results it finished.
- A ContaGen task with
find_emailsprices the email finds against the live balance before submitting them; when they do not fit it skips them, keeps the contacts it found withemailnull, and says so in its warnings. - CRM jobs (contact enrichment, company audit and contact matching) check the request fee when they start and their exact count inside the job, before anything is pushed or billed; a job that does not fit fails with the balance in its error and pushes and bills nothing.
- A prospecting run pauses as
needs_inputwithstop_reasonaccount_unavailable.
Requests run at the same time are each priced against the same balance, so concurrent requests can together spend slightly past it.
422 vs 400
Section titled “422 vs 400”Every rule about the shape of a request (required parameters, lengths, list sizes, numeric ranges, enum values, date formats, URL and UUID formats, unknown parameters) is checked before the request is processed and fails with 422. The body is the standard FastAPI validation shape: a detail array with one entry per failing field, giving the field location, a message, and an error type.
Cross-field rules are checked at the same stage and answer 422 as well, with ["query"] as the location: on /discover and /count, a state that resolves under none of the selected countries (Value error, Filter state has an invalid value {value}) and a state sent with more than one country value (Value error, State filter cannot be used with multiple countries or without a country). Contacts state filters resolve the same way but are soft: an unresolvable value is dropped, not rejected.
{ "detail": [ { "loc": ["query", "max_records"], "msg": "Input should be greater than or equal to 5", "type": "greater_than_equal" } ]}400 is reserved for checks that can only run once the request is being processed: a value that fails domain normalization (Invalid Domain), a check against a stored record (for example changing a provider’s endpoint without supplying a new API key), a custom base_url that resolves to a private address, an uploaded file that cannot be parsed or exceeds its row limit, and business rules such as an exclusion list that is too small. The body is a plain string:
{ "detail": "Invalid Domain : hubspot" }If you previously matched on 400 for length, range, or enum failures, match on 422 instead. Each endpoint page lists its validation rules.
Endpoints that take query parameters (/count, /discover, /contacts, /contacts/count, /contacts/match, /match, /bulkmatch, /append, and others) reject unknown parameters with 422 and an extra_forbidden detail, so typos are surfaced rather than silently ignored. Documented deprecated aliases (e.g. exact_match → phrase_match, vendor → tech_stack, nl_match → icp_text) continue to work for backward compatibility.