Learn // Go to production
Error handling & retries
UpdatedLast verified
Handle API errors predictably: the error response format, what each HTTP status means, search filter validation, and safe retry strategies.
When a request fails, the API returns an HTTP status and an error response with the same structure on every endpoint, including an error code you can match on programmatically.
Response format
Every error follows the same JSON structure. Match on the code field to handle specific cases in your code:
Error Response
{
"success": false,
"data": null,
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "Insufficient credits: 0 available, 1 required"
},
"metadata": {
"requestId": "00bc3fa0-348f-4955-a1c1-3c7c91234568",
"executionTimeMs": 3
}
}Error reference
| Status | What to do |
|---|---|
400 | The request is malformed: a JSON body that cannot be read, or a value the endpoint refuses, such as INVALID_IDEMPOTENCY_KEY, WEBHOOK_URL_UNREACHABLE (a webhookUrl that is not public HTTPS) or JOB_INVALID_INPUT (a filter or a cursor of the job history). Fix the request before you send it again. |
422 | Check your request body: a required field is missing or malformed (INVALID_LINKEDIN_URL is also returned for an invalid URL). On the search endpoints, see Search filter validation for the exact reason. On Bulk enrichment, UNKNOWN_FIELD names a field the body or an item does not define, with details.suggestion and details.acceptedFields. With IDEMPOTENCY_KEY_REUSED, the Idempotency-Key was already used with a different body: use a new key. |
401 | Check your API key: it may be missing, malformed, invalid, or expired (API_KEY_EXPIRED). See Authentication. |
402 | Not enough credits. A paid lookup that costs more than your balance returns INSUFFICIENT_CREDITS, and the free /check endpoints return NO_CREDITS once fewer than 2 credits are left. Bulk enrichment returns BULK_JOB_INSUFFICIENT_CREDITS when your balance does not cover a single item. Purchase more or upgrade your plan. |
403 | Your workspace may be disabled (WORKSPACE_DISABLED), your key may lack a required permission (INSUFFICIENT_PERMISSIONS), or your key reached its total credit limit (KEY_CREDIT_LIMIT_REACHED): edit the key in API Keys to add the permission, or to raise or reset the limit. Bulk enrichment returns BULK_JOB_PHONE_FINDER_DISABLED when it asks for phone numbers on a workspace where phone finding is not enabled, and Cancel a job returns JOB_STARTED_BY_A_PERSON for a job a person started in the app: stop it there. |
404 | No result found for your query. Some endpoints still charge credits; see API pricing. |
409 | With IDEMPOTENCY_KEY_IN_USE, a request with the same Idempotency-Key is still being processed: wait the Retry-After seconds, then retry with the same key to receive the first request's response. See Find Email. With JOB_RESULTS_NOT_READY, the job has not finished: follow it with Get a job and download the results once links.results is set. With JOB_NOT_CANCELLABLE, the job already ended: there is nothing to retry. With JOB_RESULTS_FORMAT_UNAVAILABLE, the results of the job were stored before NDJSON and reading by page existed: download them as CSV. |
410 | The endpoint was retired. Call the endpoint named in error.details.alternative instead. |
413 | The JSON body is over 25 MB (REQUEST_BODY_TOO_LARGE). Send a smaller body: retrying the same one will not help. |
429 | Rate limit reached. Wait the number of seconds in error.details.retryAfter, then retry (see below). The limits of each plan are in Rate limits. With KEY_CREDIT_LIMIT_REACHED, the key reached its daily or monthly credit limit: see API key management. |
451 | This person or company asked not to be processed under GDPR or CCPA. The lookup is permanently blocked. |
500 | Unexpected error on our side. Retry with backoff (see below). |
502 | A provider we rely on failed for this request. Retry with backoff (see below). |
503 | Our service is temporarily unavailable. Retry with backoff (see below). With BULK_QUEUE_UNAVAILABLE, nothing was created: send the Bulk enrichment request again shortly, with the same Idempotency-Key. |
504 | The lookup took too long and timed out. Retry with backoff (see below). |
Search filter validation
The search endpoints validate every filter before running the query. A rejected request returns 422, costs 0 credits, and never reaches the database. Without this check, a filter we cannot honor would return an empty page that looks like a legitimate "no results".
error.details.errors always lists every problem found, so one round trip is enough to fix a whole payload.
error.code | Meaning |
|---|---|
UNKNOWN_FIELD | The field does not exist. Unknown fields are rejected rather than ignored, because an ignored filter returns unfiltered results. details.suggestion gives the closest accepted field, details.acceptedFields lists them all. A page sent to People Search or Company Search, which paginate with a cursor, suggests cursor. |
INVALID_FILTER_VALUE | The value is outside a closed list. This covers industry and country values plus the People Search currentFunction, currentSubFunction and currentSeniority taxonomies, its contract types and the changedJobsWithinDays windows, as well as keywords or schoolName without a single word. details.suggestions proposes up to three valid values, or the eight contract types when none is close. |
EMPTY_FILTER_VALUE | A blank string, empty array or empty object was sent. Remove the field instead. |
FILTER_VALUES_LIMIT_EXCEEDED | A filter carries more than 20 values. Split an include list into searches of at most 20 values and merge the results. Splitting does not work for an exclude... list, because each search only applies its own list: shorten it, or use a taxonomy filter such as excludeCurrentFunction. For countries, use location.area. |
INVALID_RANGE | A min/max pair is negative, fractional, inverted (min greater than max), or above the maximum of its filter, given in details.max, such as 2100 for the Company Search foundedYear. |
INCOMPLETE_RANGE_FILTER | followersCount used as the only filter requires both bounds. |
INVALID_PAGINATION | perPage is not a whole number. |
PAGINATION_LIMIT_EXCEEDED | perPage is above 100. details carries field and max. |
INVALID_ID_FORMAT | currentCompanyId is not a com_ identifier returned by our API, or schoolId is not a sch_ identifier. |
INVALID_DATE_FORMAT | maxDataAgeDate is not an ISO 8601 date-time. |
CONTRADICTORY_FILTERS | The same value appears in an included and excluded filter, which can never match. |
NO_FILTER_PROVIDED | The body carries pagination only (cursor / perPage), or maxDataAgeDate is the only filter of a Company Search, which it can narrow but not select on its own. |
INVALID_SEARCH_FILTERS | Several of the above at once. Read details.errors. |
{
"success": false,
"data": null,
"error": {
"code": "INVALID_FILTER_VALUE",
"message": "\"Technology\" is not an accepted value for \"industry\". Did you mean \"Technology, Information and Internet\", \"Technology, Information and Media\"? This filter is matched exactly, so an unlisted value can never return a result.",
"details": {
"field": "industry",
"value": "Technology",
"reason": "not_in_accepted_values",
"suggestions": ["Technology, Information and Internet", "Technology, Information and Media"],
"documentation": "https://docs.reversecontact.com/guides/industries"
}
}
}Values are compared tolerantly before being rejected: case, surrounding spaces, accents and & versus and are normalized for you. Only an unknown value is refused.
When to retry
These statuses are safe to retry, and they cost 0 credits:
429: you've hit a rate limit. Wait the number of seconds inerror.details.retryAfter, then retry. WithKEY_CREDIT_LIMIT_REACHED, that wait runs until 00:00 UTC or the 1st of the month: pause the work rather than retrying in a loop.500,502,503and504: a temporary issue on our side or at a provider we rely on. Retry with exponential backoff (below), and stop after a few attempts, for example 5, then alert. Incidents are posted on status.reversecontact.com, where you can subscribe to updates by email, Slack, webhook or RSS.409withIDEMPOTENCY_KEY_IN_USE: a request with the sameIdempotency-Keyis still being processed. Wait theRetry-Afterseconds, then retry with the same key. A409 JOB_RESULTS_NOT_READYasks you to wait for the job instead, and a409 JOB_NOT_CANCELLABLEor409 JOB_RESULTS_FORMAT_UNAVAILABLEis final.
All other errors (400, 401, 402, 403, 404, 410, 413, 422, 451) require a change to your request, your key or your credit balance before retrying.
Find Email and Find Phone debit credits when their job is created, and Bulk enrichment reserves them when it creates its job. Before you retry one of them after a timeout or a dropped connection, make sure the request carries an Idempotency-Key: a retry with the same key returns the first response instead of starting a second lookup or job. See Find Email.
Wait a little longer between each retry to give the server time to recover:
Retry delay
const delay = Math.min(1000 * 2 ** attempt, 15000)
const jitter = Math.floor(Math.random() * 250)
await new Promise((r) => setTimeout(r, delay + jitter))delay = min(1 * 2 ** attempt, 15)
jitter = random.uniform(0, 0.25)
time.sleep(delay + jitter)Timeouts and aborted requests
Set your client timeout to at least 45 seconds on the search and count endpoints, and 60 seconds on prompt search. When a search runs out of time, we answer with a JSON 504 before those limits, and it costs 0 credits. A shorter client timeout can cut the connection just before that answer arrives.
Closing the connection does not cancel the request. It keeps running on our side and can still be billed, even though you never receive the page. A retry after an abort can then bill the same page twice, so raise a timeout that fires too early rather than aborting and retrying.
Credits and errors
All error responses (401, 402, 403, 409, 410, 413, 422, 429, 451, 500, 502, 503, 504) cost 0 credits.
The 404 status is the only exception: some endpoints charge credits when the lookup was performed but no result was found. See API pricing for the full breakdown.
FAQ
Which Reverse Contact API errors are safe to retry?
429 (rate limit reached) is safe to retry after the number of seconds in error.details.retryAfter. 500, 502, 503 and 504 (a temporary issue on our side or at a provider) are safe to retry with exponential backoff, for example a delay that doubles from 1 second up to 15 seconds, plus up to 250 ms of random jitter, for a few attempts. All other errors require a change to your request, your key or your credit balance before retrying. See When to retry.
Do failed API requests cost credits?
Error responses (401, 402, 403, 409, 410, 413, 422, 429, 451, 500, 502, 503, 504) cost 0 credits. 404 is the only exception: some endpoints charge credits when the lookup was performed but no result was found. See API pricing.
Can an aborted request be billed?
Yes. Closing the connection does not cancel the request, so it can still be billed. Set your client timeout to at least 45 seconds on the search and count endpoints, and 60 seconds on prompt search. A 504 from us costs 0 credits.
What is the difference between INSUFFICIENT_CREDITS and NO_CREDITS?
INSUFFICIENT_CREDITS and NO_CREDITS?Both codes come with a 402 status. INSUFFICIENT_CREDITS means a paid lookup costs more than your remaining balance, and error.details gives the credits required and available. NO_CREDITS blocks the free /check endpoints once fewer than 2 credits are left. Both clear when you top up by purchasing more credits or upgrading your plan.
Why does a search request return 422 instead of an empty result?
422 instead of an empty result?The search endpoints validate every filter before running the query, because a filter that cannot be honored would otherwise return an empty page that looks like a legitimate "no results". A rejected search returns 422, costs 0 credits, and lists every problem found in error.details.errors. See Search filter validation.
What does a 451 error mean?
451 error mean?A 451 means this person or company asked not to be processed under GDPR or CCPA, and the lookup is permanently blocked. Profile and company lookups answer 451 whichever input names them: profile URL, email, internal id, domain or name. Find Email and Find Phone answer 451 when given the profile URL or internal id. Like the other error responses, a 451 costs 0 credits.