Error Codes
On failure, check the HTTP status code first, then the body. Business errors carry a machine-readable code; framework-level failures carry only a message.
Error Body
Section titled “Error Body”Business errors always return a code you can branch on programmatically:
{ "code": "insufficient_balance", "message": "Insufficient balance for this submission."}The code is a contract token — never translate it, never display it, and never branch on the message text, which is prose and may be reworded.
Error code reference
Section titled “Error code reference”The API returns exactly these 22 codes, with these statuses.
| code | HTTP | Description |
|---|---|---|
unauthorized | 401 | API key missing, invalid, or inactive. |
account_banned | 403 | Account suspended. Contact support. |
invalid_realtime_request | 400 | Malformed realtime request (e.g. a phone number without a + international prefix while country is omitted). |
idempotency_key_required | 400 | Realtime requests require the Idempotency-Key header. |
invalid_idempotency_key | 422 | Idempotency-Key must be 1–128 characters from letters, digits and . _ : - |
idempotency_conflict | 409 | The same Idempotency-Key was used with a different request payload. |
request_in_progress | 409 | An identical realtime request is still being processed. Retry shortly (Retry-After: 1). |
empty_input | 422 | The uploaded file contains no usable rows. |
product_not_found | 422 | The submitted product slug does not exist or is inactive. |
product_mode_not_supported | 422 | This endpoint does not accept this product mode (realtime products cannot be used on bulk endpoints). |
invalid_country | 422 | The product does not support the submitted country. |
too_few_numbers | 422 | The submission is below the product minimum. |
too_many_numbers | 422 | The submission exceeds the platform maximum. |
insufficient_balance | 402 | Not enough balance to cover this submission. Top up and retry. |
realtime_disabled | 403 | Realtime detection is not enabled for this account. |
realtime_rate_limited | 429 | Realtime rate limit exceeded. Respect retry_after. |
product_pricing_unavailable | 503 | Pricing for this product is not configured yet. Contact support. |
detection_capacity_unavailable | 503 | No detection capacity available right now. Retry later. |
realtime_route_unavailable | 503 | No realtime route available. Retry later. |
realtime_fact_cache_unavailable | 503 | A realtime dependency is temporarily unavailable. Retry later. |
realtime_rate_limiter_unavailable | 503 | The rate limiter is temporarily unavailable. Retry later. |
internal_error | 500 | Unexpected server error. Safe to retry with the same Idempotency-Key. |
Failures that carry no code
Section titled “Failures that carry no code”Not every failure has a code. The task endpoints report four conditions by HTTP status and message alone, because those failures are raised before the business-error path is reached:
| HTTP | Condition | Body |
|---|---|---|
404 | The task_id does not exist, or belongs to another account | message only |
404 | GET /v1/tasks/{token}/download — the result file is unavailable, expired, or no longer in a downloadable state | message only |
422 | GET /v1/tasks/{token}/result — the result is not ready: the task is not complete, the file has not been generated, or it has expired | message only |
422 | Request parameters failed validation | { "message": …, "errors": { … } } |
Branch on the HTTP status for these. The message is for your logs and for a human reading an incident, not for control flow.
Retrying
Section titled “Retrying”The right response to a failure depends on whether repeating the request could produce a different answer.
| Group | Codes | What to do |
|---|---|---|
| Action required first — an unchanged retry repeats the same failure | unauthorized, account_banned, invalid_realtime_request, idempotency_key_required, invalid_idempotency_key, idempotency_conflict, empty_input, product_not_found, product_mode_not_supported, invalid_country, too_few_numbers, too_many_numbers, insufficient_balance, realtime_disabled, product_pricing_unavailable | Fix the request, the key, or the balance, then send a new one. Retrying is wasted traffic. |
| Wait, then retry — transient, and the same request should succeed later | realtime_rate_limited, request_in_progress, detection_capacity_unavailable, realtime_route_unavailable, realtime_fact_cache_unavailable, realtime_rate_limiter_unavailable | Honour Retry-After when present, then back off exponentially. The request itself is fine. |
| Outcome uncertain — the request may or may not have applied | internal_error | Retry the same body with the same Idempotency-Key. |
internal_error is the one case where you cannot tell from the response whether the work happened. Retry it with the same Idempotency-Key: a replay returns the stored result and never charges twice. Generating a fresh key is what turns one charge into two.
The two codeless task failures map onto these groups by status: a 404 on a task endpoint means the token is wrong, and the fix is the same as “action required”; a 422 on the result endpoint is the “wait, then retry” case — it means poll again, not fix something. The polling loop on Task status handles it by reading status rather than by catching an error.
See Rate limits for the full response-header set on a 429.