Errors

Every MobileValidate API error code with its HTTP status, whether to retry, what causes it and how to fix it, plus the error response format.

Last updated

View as MarkdownError catalog (JSON)

When a whole request can't be processed, the API returns a non-2xx status and one JSON error object. Each error has a stable code to branch on, a retryable flag, and a request_id to quote to support. Problems with single rows, such as invalid, duplicate, suppressed, unknown or unsupported numbers, are reported inside results instead. They never fail the request and are never billed.

What does an error response look like?

JSON
{
  "error": {
    "code": "service_disabled",
    "message": "The check 'signal.registered' is available in bulk jobs only (POST /v1/jobs).",
    "status": 403,
    "retryable": false,
    "param": "checks[0]",
    "doc_url": "https://mobilevalidate.com/docs/errors#service_disabled",
    "request_id": "req_0VWF4BN8PIsgkUh3EvUj",
    "suggestion": "Create a bulk job (POST /v1/jobs) for this check, or remove it from checks."
  }
}
  • code is stable. Branch on it, not on message, whose wording may change.
  • param points to the field at fault (checks[1], numbers, emails[3], max_cost.amount), or is null. Your values are never echoed back.
  • retryable says whether sending the same request again later can succeed.
  • request_id also comes back as the X-Request-Id header on every response.
  • suggestion (optional) is a plain-English hint on how to fix the request, for example a missing country code or a likely typo in an e-mail domain. It never repeats the value you sent. Show it to developers; don't parse it.

Which codes can the API return?

CodeHTTPRetryable
invalid_request400no
too_many_numbers400no
test_number_only400no
invalid_cursor400no
unauthorized401no
insufficient_balance402no
cost_limit_exceeded402no
insufficient_scope403no
service_disabled403no
sandbox_magic_only403no
suspected_enumeration403no
not_found404no
idempotency_key_reused409no
idempotency_request_in_progress409yes
test_key_exists409no
payload_too_large413no
rate_limited429yes
daily_cap_reached429yes (after the reset)
spend_cap_reached429no
internal_error500yes
temporarily_unavailable503yes

What does each code mean?

invalid_request

400, not retryable. The body is malformed JSON, breaks the schema (missing or unknown field, wrong type), or uses an unknown check code. It is also returned when e-mails are sent without an e-mail check or numbers without a phone check (param: "checks"), for more than 20 checks, and when identifiers × applicable checks exceed 2,000 per lookup or 100,000 per job. Fix: read param and message, and correct that field. GET /v1/services lists valid codes.

too_many_numbers

400, not retryable. More than 100 numbers + e-mails on POST /v1/lookup, more than 50,000 on POST /v1/jobs (or in an uploaded CSV, param: "file"), or more than 10 in a job or estimate with the public sandbox key. Fix: split the list, or use a bulk job for anything over 100.

test_number_only

400, not retryable. A live key was used with a test number ("Test numbers can only be used with test keys.") or a test.mobilevalidate.com address ("Test e-mail addresses can only be used with test keys."). Fix: use your test key for test data and real identifiers with live keys.

invalid_cursor

400, not retryable. The after cursor for job results is malformed. Fix: use next_cursor from the previous page exactly as returned, or start again without after.

unauthorized

401, not retryable. The key is missing, malformed, unknown, revoked or expired, or was used from an IP address outside its allowlist. All cases look the same on purpose. Fix: check the Authorization: Bearer header, the key prefix and your outgoing IP address, including IPv6 (see authentication).

insufficient_balance

402, not retryable. Your balance can't cover the maximum possible cost of the request, which is reserved before checking. Fix: top up, or send fewer identifiers or checks. Cached and inconclusive answers are released afterwards, so the final charge is often lower than the reservation.

cost_limit_exceeded

402, not retryable. The maximum possible cost is higher than the max_cost you sent. Fix: raise max_cost or shrink the request. POST /v1/jobs/estimate shows the maximum cost for free.

insufficient_scope

403, not retryable. The key doesn't have the scope this endpoint needs, for example webhooks:manage or account:read. Fix: use a key with that scope, or ask for one.

service_disabled

403, not retryable. One of the checks is switched off, isn't enabled for your account, or is bulk only and was sent to POST /v1/lookup ("… is available in bulk jobs only (POST /v1/jobs)"). param names the check (checks[i]); it is null when real-time checks are not enabled for the whole account. Fix: run bulk-only services in a job, or remove the check. Services marked coming soon, such as number.hlr, give this answer until they launch.

sandbox_magic_only

403, not retryable. The public sandbox key (the key in the docs examples) accepts only the documented test numbers and e-mail addresses. param names the first other identifier (numbers[2], emails[0]); an invalid number counts as another identifier. The sandbox's other limits have their own codes: jobs over 10 rows return too_many_numbers, and webhooks return invalid_request. Fix: use a test value, or get a personal test key to test your own data. The suggestion field links both.

suspected_enumeration

403, not retryable. The request looks like a scan: 20 or more consecutive numbers (param: "numbers"), 20 or more addresses on one domain differing only by digits or separators, or (live keys) 50 or more such addresses from your account in one UTC day (param: "emails"). Fix: only check identifiers you actually hold, such as customers, sign-ups and leads. See rate limits and abuse.

not_found

404, not retryable. The id doesn't exist, belongs to another account, or its results were purged ("Results for this job were purged."). For privacy, we don't say which. An unknown path also returns not_found, before the key is checked; the suggestion names the closest endpoint. Fix: check the id and the path, and fetch results within your retention period.

idempotency_key_reused

409, not retryable. The Idempotency-Key was already used within 24 hours with a different request body. Fix: use a new key for each distinct request, and reuse a key only for exact retries.

idempotency_request_in_progress

409, retryable. The first request with this Idempotency-Key is still running. Fix: wait briefly and retry with the same key and body. You'll get the stored response.

test_key_exists

409, not retryable. POST /v1/test-keys (the "get a test key" form): no more test keys can be issued for this e-mail address right now (at most 3 in 30 days). Keys already issued are never shown again. Fix: use a key you received earlier, or request access and we'll set you up.

payload_too_large

413, not retryable. The body is larger than 1 MB (4 MB for POST /v1/jobs and /v1/jobs/estimate). Fix: split the list into several jobs.

rate_limited

429, retryable. More than 10 requests per second (burst 20) on this key, or, for the public sandbox key, more than 30 requests per minute or 1,000 per day from your IP address. Fix: wait for the Retry-After seconds, then retry with backoff. Batch numbers into one request instead of sending one request per number.

daily_cap_reached

429, retryable after the reset. Your account's daily cap on identifiers is used up. Caps reset at 00:00 UTC, and Retry-After counts down to it. Fix: retry after the reset shown in GET /v1/limits, or ask for a higher cap.

spend_cap_reached

429, not retryable. The key's daily spend cap (agent keys) has been reached. It resets at 00:00 UTC. Fix: wait for the reset, raise the cap or use another key. Retrying right away won't help.

internal_error

500, retryable. Something failed on our side. Fix: retry with backoff, using the same Idempotency-Key so the retry is safe. If it keeps happening, send us the request_id.

temporarily_unavailable

503, retryable. The service is overloaded or every route for a check is down. Fix: retry after Retry-After. A request that fails this way has not been charged.

Are there codes outside the HTTP API?

  • MCP server: confirmation_required means a tool call would spend more than the confirmation threshold (default USD 1.00) or covers more than 100 numbers. The agent should show the amount to the user and call again with confirm_max_cost. See MCP.
  • SDK: client-side codes missing_api_key, connection_error, timeout, invalid_response and invalid_argument never come from the API itself. See SDK.

Frequently asked questions

Is an unknown result an error?

No. Per-row problems (invalid, duplicate, suppressed, unknown, unsupported country) never fail the request and are never billed. Errors are only for problems with the request as a whole.

Which errors should my client retry?

Only those with retryable: true: rate_limited, daily_cap_reached (after the reset), idempotency_request_in_progress, internal_error and temporarily_unavailable. Honour Retry-After and use backoff with jitter.