Skip to content

Errors

The DiscoLike API uses conventional HTTP response codes to indicate success or failure of requests.

Error CodeMeaning
400Bad Request — The request could not be processed against your data or the input you sent
401Unauthorized — Your API key is wrong
403Forbidden — You don’t have access to this resource, or the request costs more than the remaining balance (see Insufficient balance)
404Not Found — The specified resource could not be found
422Unprocessable Entity — The request failed parameter validation
429Too Many Requests — You’re making too many requests, or have too much work queued or running (see Rate Limits)
500Internal Server Error — We had a problem with our server
503Service Unavailable — We’re temporarily offline for maintenance, or a billed request could not be priced (Unable to price this request, try again); retry it

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"
}
}
FieldTypeDescription
codestringAlways INSUFFICIENT_BALANCE
messagestringHuman-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_usdnumberBalance left in the current cycle
max_affordable_recordsintegerThe 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_recordsintegerThe size the request asked for, in the same unit
upgrade_urlstringWhere 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_emails prices the email finds against the live balance before submitting them; when they do not fit it skips them, keeps the contacts it found with email null, 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_input with stop_reason account_unavailable.

Requests run at the same time are each priced against the same balance, so concurrent requests can together spend slightly past it.

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.