Errors
All v2 endpoints use conventional HTTP status codes and a consistent JSON envelope.
| Status | Meaning |
|---|---|
200 | Success |
400 | Bad request - validation failed; the response body lists the offending fields |
401 | Unauthorized - missing or invalid x-api-key header |
402 | Payment required - not enough credits for the requested operation |
408 | Request timeout - the lookup took too long; safe to retry |
429 | Too many requests - demo account only; back off and retry later |
5xx | Server error - safe to retry with exponential backoff |
Error responses share one envelope shape: success is false and errors is an array of
{ name, message } objects (validation errors also carry a payload with the offending field).
data is usually null, with one exception noted below.
"Not found" results are HTTP
200, not404People Enrich, Company Enrich, and Email Finder return a well-formed
200response withsuccess: falseanderrors: [{ "name": "NOT_FOUND", ... }]when a valid, well-formed request simply finds no match - there is no dedicated404for this case. Email Finder's not-found response also keeps a non-nulldataobject (email: nullplus whatever domain information was resolved). Checksuccessanderrors, not the HTTP status, to detect a miss.
400 - Bad request
Each failing field gets its own entry in errors, named VALIDATION_ERROR, with the offending
field's full path in payload.field. payload.code says what kind of mistake it is - always
check the code, not only the name:
payload.code | Meaning | Extra payload fields |
|---|---|---|
unknown_field | The field does not exist on this endpoint | did_you_mean (when close), allowed_fields |
value_not_allowed | A value is outside the field's closed vocabulary | invalid_values, allowed_values, enum |
missing_field | A required field is absent or empty | |
wrong_type | The value has the wrong type (for example a string page) | |
out_of_range | A number, length or list size is outside its bounds | |
invalid_format | A string does not match the expected format (id, URL, ...) | |
conflicting_fields | Two fields that mean the same thing were both sent | |
invalid_value | Any other rule the value breaks |
For value_not_allowed, allowed_values lists the whole vocabulary when it is short. When the
vocabulary is one of the enums (seniorities, industries, departments, ...),
enum names it, and large ones such as industries are not inlined - read them from
GET /v2/enums/<enum> instead.
The example below sends People Search a seniority outside the vocabulary and a misspelled company field:
{
"success": false,
"data": null,
"metadata": {
"timestamp": "2026-10-02T09:00:00.000Z"
},
"errors": [
{
"name": "VALIDATION_ERROR",
"message": "filters.person_seniority.include: \"c_level\" is not an accepted value. Accepted: Owner, Partner, Chief Officer, VP, Director, Senior, Manager, Entry, Intern, Unpaid; the accepted values are listed at GET /v2/enums/seniorities",
"payload": {
"field": "filters.person_seniority.include",
"code": "value_not_allowed",
"received_value": [
"c_level"
],
"received_type": "object",
"invalid_values": [
"c_level"
],
"allowed_values": [
"Owner",
"Partner",
"Chief Officer",
"VP",
"Director",
"Senior",
"Manager",
"Entry",
"Intern",
"Unpaid"
],
"enum": "seniorities",
"constraints": {
"isIn": "each value in include must be one of the following values: Owner, Partner, Chief Officer, VP, Director, Senior, Manager, Entry, Intern, Unpaid"
}
}
},
{
"name": "VALIDATION_ERROR",
"message": "filters.company.domainz is not a field of this request - did you mean filters.company.domain?",
"payload": {
"field": "filters.company.domainz",
"code": "unknown_field",
"received_value": {
"include": [
"stripe.com"
]
},
"received_type": "object",
"did_you_mean": "filters.company.domain",
"allowed_fields": [
"id",
"domain",
"name",
"linkedin_id"
],
"constraints": {
"whitelistValidation": "property domainz should not exist"
}
}
}
]
}Filter-level rules surface as a plainer
400A handful of rules aren't expressed as per-field decorators - for example "at least one include filter is required" on People Search. Those still return
400/VALIDATION_ERROR, but as a single{ name, message }entry with nopayload.
401 - Unauthorized
Returned when the x-api-key header is missing or invalid.
{
"success": false,
"data": null,
"metadata": {
"timestamp": "2026-06-18T09:12:44.000Z"
},
"errors": [
{
"name": "UNAUTHORIZED",
"message": "Unauthorized"
}
]
}402 - Payment required
Returned once your workspace's credit balance reaches zero, before the request is even processed.
The response also sets two headers: X-Limit-Reached: true and X-Limit-Type: credits.
{
"success": false,
"data": null,
"metadata": {
"timestamp": "2026-07-25T14:02:31.000Z"
},
"errors": [
{
"name": "PAYMENT_REQUIRED",
"message": "Workspace has reached its credit limit"
}
]
}Some endpoints also run a narrower pre-flight check against the specific credit type a request
would spend (for example People Search only checks email credits when you pass
enrich_email: true), which fails the same way with a more specific message such as
"Insufficient email credits: have 0, need 3".
408 - Request timeout
Email Finder and Email Verifier return 408 when the underlying lookup takes too long. Retry
after a short delay.
{
"success": false,
"data": null,
"metadata": {
"timestamp": "2026-07-25T14:02:31.000Z"
},
"errors": [
{
"name": "TIMEOUT",
"message": "Email search is taking longer than expected. Please retry your request in a few minutes."
}
]
}429 - Too many requests
Only applies to the shared demo account, capped at 1500 requests per hour per requesting IP address. Regular workspace API keys are not rate-limited.
{
"success": false,
"data": null,
"metadata": {
"timestamp": "2026-07-25T14:02:31.000Z"
},
"errors": [
{
"name": "RATE_LIMIT",
"message": "Demo user requests limit"
}
]
}5xx - Server error
Returned when a downstream dependency is unavailable - for example, People Search and Company
Search return 500 with this message when the search backend can't be reached. Safe to retry
with exponential backoff.
{
"success": false,
"data": null,
"metadata": {
"timestamp": "2026-07-25T14:02:31.000Z"
},
"errors": [
{
"name": "INTERNAL_ERROR",
"message": "Search backend unavailable"
}
]
}An unexpected, unhandled error (rather than one the API explicitly detected and reported) returns
the same 500 / INTERNAL_ERROR shape with a generic "Internal server error" message instead,
so as not to leak internal details.
