{
  "schema_version": "1.0",
  "name": "MobileValidate API error catalog",
  "api_base": "https://api.mobilevalidate.com/v1",
  "docs_url": "https://mobilevalidate.com/docs/errors",
  "error_format": {
    "description": "Non-2xx responses carry one JSON object `error`. Branch on `code` (stable), not on `message` (wording may change). `request_id` is also returned as the X-Request-Id header.",
    "fields": {
      "code": "Stable machine-readable error code (see `errors`).",
      "message": "Human-readable explanation; submitted values are never echoed.",
      "status": "HTTP status code.",
      "retryable": "Whether sending the same request again later can succeed.",
      "param": "The request field at fault, or null.",
      "suggestion": "Optional plain-English hint on how to fix the request (never echoes your input). May be absent.",
      "doc_url": "Link to this code on the errors page.",
      "request_id": "Id to quote to support."
    }
  },
  "retry_guidance": "Retry only codes with retryable: true. Honour Retry-After, use exponential backoff with jitter, and resend POSTs with the same Idempotency-Key.",
  "row_level_note": "Per-row problems (invalid, duplicate, suppressed, unknown, unsupported country) never fail the request and are never billed; they are reported inside `results`.",
  "errors": [
    {
      "code": "invalid_request",
      "http_status": 400,
      "retryable": false,
      "meaning": "The body is malformed JSON, breaks the schema (missing or unknown field, wrong type) or uses an unknown check code. Also returned for e-mails without an e-mail check, numbers without a phone check, more than 20 checks, or identifiers × applicable checks above 2,000 per lookup or 100,000 per job.",
      "fix": "Read `param` and `message` and correct that field. GET /v1/services lists valid check codes.",
      "param": "The field at fault, e.g. checks[1], checks, numbers, emails[3], max_cost.amount.",
      "doc_url": "https://mobilevalidate.com/docs/errors#invalid_request",
      "example": {
        "error": {
          "code": "invalid_request",
          "message": "…",
          "status": 400,
          "retryable": false,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#invalid_request",
          "request_id": "req_…"
        }
      }
    },
    {
      "code": "too_many_numbers",
      "http_status": 400,
      "retryable": false,
      "meaning": "More than 100 numbers + e-mails on POST /v1/lookup, more than 50,000 on POST /v1/jobs (or in an uploaded CSV), or more than 10 in a job or estimate with the public sandbox key.",
      "fix": "Split the list, or use a bulk job (POST /v1/jobs) for anything over 100 identifiers.",
      "doc_url": "https://mobilevalidate.com/docs/errors#too_many_numbers",
      "example": {
        "error": {
          "code": "too_many_numbers",
          "message": "…",
          "status": 400,
          "retryable": false,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#too_many_numbers",
          "request_id": "req_…"
        }
      }
    },
    {
      "code": "test_number_only",
      "http_status": 400,
      "retryable": false,
      "meaning": "A live key was used with a test number or a test.mobilevalidate.com e-mail address.",
      "fix": "Use a test key (mv_test_…) for test data and real identifiers with live keys.",
      "doc_url": "https://mobilevalidate.com/docs/errors#test_number_only",
      "example": {
        "error": {
          "code": "test_number_only",
          "message": "…",
          "status": 400,
          "retryable": false,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#test_number_only",
          "request_id": "req_…"
        }
      }
    },
    {
      "code": "invalid_cursor",
      "http_status": 400,
      "retryable": false,
      "meaning": "The `after` cursor for job results is malformed or belongs to another listing.",
      "fix": "Pass `next_cursor` from the previous page exactly as returned, or start again without `after`.",
      "doc_url": "https://mobilevalidate.com/docs/errors#invalid_cursor",
      "example": {
        "error": {
          "code": "invalid_cursor",
          "message": "…",
          "status": 400,
          "retryable": false,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#invalid_cursor",
          "request_id": "req_…"
        }
      }
    },
    {
      "code": "unauthorized",
      "http_status": 401,
      "retryable": false,
      "meaning": "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 (IPv4 and IPv6).",
      "doc_url": "https://mobilevalidate.com/docs/errors#unauthorized",
      "example": {
        "error": {
          "code": "unauthorized",
          "message": "…",
          "status": 401,
          "retryable": false,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#unauthorized",
          "request_id": "req_…"
        }
      }
    },
    {
      "code": "insufficient_balance",
      "http_status": 402,
      "retryable": false,
      "meaning": "The balance cannot 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.",
      "doc_url": "https://mobilevalidate.com/docs/errors#insufficient_balance",
      "example": {
        "error": {
          "code": "insufficient_balance",
          "message": "…",
          "status": 402,
          "retryable": false,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#insufficient_balance",
          "request_id": "req_…"
        }
      }
    },
    {
      "code": "cost_limit_exceeded",
      "http_status": 402,
      "retryable": false,
      "meaning": "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.",
      "doc_url": "https://mobilevalidate.com/docs/errors#cost_limit_exceeded",
      "example": {
        "error": {
          "code": "cost_limit_exceeded",
          "message": "…",
          "status": 402,
          "retryable": false,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#cost_limit_exceeded",
          "request_id": "req_…"
        }
      }
    },
    {
      "code": "insufficient_scope",
      "http_status": 403,
      "retryable": false,
      "meaning": "The key does not have the scope this endpoint needs, for example webhooks:manage or account:read.",
      "fix": "Use a key with that scope, or ask for one.",
      "doc_url": "https://mobilevalidate.com/docs/errors#insufficient_scope",
      "example": {
        "error": {
          "code": "insufficient_scope",
          "message": "…",
          "status": 403,
          "retryable": false,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#insufficient_scope",
          "request_id": "req_…"
        }
      }
    },
    {
      "code": "service_disabled",
      "http_status": 403,
      "retryable": false,
      "meaning": "A requested check is switched off, not enabled for the account, coming soon, or bulk only and was sent to POST /v1/lookup.",
      "fix": "Run bulk-only services in a job (POST /v1/jobs), or remove the check.",
      "param": "checks[i] — the check at fault.",
      "doc_url": "https://mobilevalidate.com/docs/errors#service_disabled",
      "example": {
        "error": {
          "code": "service_disabled",
          "message": "…",
          "status": 403,
          "retryable": false,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#service_disabled",
          "request_id": "req_…"
        }
      }
    },
    {
      "code": "sandbox_magic_only",
      "http_status": 403,
      "retryable": false,
      "meaning": "The public sandbox key only answers the documented magic test numbers and e-mail addresses; any other identifier (including an invalid one) is rejected. Its other limits have their own codes: jobs over 10 rows → too_many_numbers, webhooks → invalid_request.",
      "fix": "Use a value from /docs/test-values, or get a personal test key at /get-test-key to test any number in test mode.",
      "param": "numbers[i] or emails[i]: the first identifier that is not a documented test value.",
      "doc_url": "https://mobilevalidate.com/docs/errors#sandbox_magic_only",
      "example": {
        "error": {
          "code": "sandbox_magic_only",
          "message": "…",
          "status": 403,
          "retryable": false,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#sandbox_magic_only",
          "request_id": "req_…"
        }
      }
    },
    {
      "code": "suspected_enumeration",
      "http_status": 403,
      "retryable": false,
      "meaning": "The request looks like a scan: 20 or more consecutive numbers, 20 or more addresses on one domain differing only by digits or separators, or (live keys) 50 or more such addresses from the account in one UTC day.",
      "fix": "Only check identifiers you actually hold (customers, sign-ups, leads). See /docs/rate-limits-and-abuse.",
      "param": "numbers or emails.",
      "doc_url": "https://mobilevalidate.com/docs/errors#suspected_enumeration",
      "example": {
        "error": {
          "code": "suspected_enumeration",
          "message": "…",
          "status": 403,
          "retryable": false,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#suspected_enumeration",
          "request_id": "req_…"
        }
      }
    },
    {
      "code": "not_found",
      "http_status": 404,
      "retryable": false,
      "meaning": "The id does not exist, belongs to another account, or its results were purged. For privacy the API does not say which.",
      "fix": "Check the id, and fetch results within your retention period.",
      "doc_url": "https://mobilevalidate.com/docs/errors#not_found",
      "example": {
        "error": {
          "code": "not_found",
          "message": "…",
          "status": 404,
          "retryable": false,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#not_found",
          "request_id": "req_…"
        }
      }
    },
    {
      "code": "idempotency_key_reused",
      "http_status": 409,
      "retryable": false,
      "meaning": "The Idempotency-Key was already used within 24 hours with a different request body.",
      "fix": "Use a new key for each distinct request; reuse a key only for exact retries.",
      "doc_url": "https://mobilevalidate.com/docs/errors#idempotency_key_reused",
      "example": {
        "error": {
          "code": "idempotency_key_reused",
          "message": "…",
          "status": 409,
          "retryable": false,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#idempotency_key_reused",
          "request_id": "req_…"
        }
      }
    },
    {
      "code": "idempotency_request_in_progress",
      "http_status": 409,
      "retryable": true,
      "meaning": "The first request with this Idempotency-Key is still running.",
      "fix": "Wait briefly and retry with the same key and body to receive the stored response.",
      "doc_url": "https://mobilevalidate.com/docs/errors#idempotency_request_in_progress",
      "example": {
        "error": {
          "code": "idempotency_request_in_progress",
          "message": "…",
          "status": 409,
          "retryable": true,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#idempotency_request_in_progress",
          "request_id": "req_…"
        }
      }
    },
    {
      "code": "test_key_exists",
      "http_status": 409,
      "retryable": false,
      "meaning": "POST /v1/test-keys: no more test keys can be issued for this e-mail address right now (at most 3 in 30 days). Keys are shown only once and are never sent again.",
      "fix": "Use a test key you received earlier, or request access at /request-access.",
      "doc_url": "https://mobilevalidate.com/docs/errors#test_key_exists",
      "example": {
        "error": {
          "code": "test_key_exists",
          "message": "…",
          "status": 409,
          "retryable": false,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#test_key_exists",
          "request_id": "req_…"
        }
      }
    },
    {
      "code": "payload_too_large",
      "http_status": 413,
      "retryable": false,
      "meaning": "The body is larger than 1 MB (4 MB for POST /v1/jobs and /v1/jobs/estimate).",
      "fix": "Split the list into several requests or jobs.",
      "doc_url": "https://mobilevalidate.com/docs/errors#payload_too_large",
      "example": {
        "error": {
          "code": "payload_too_large",
          "message": "…",
          "status": 413,
          "retryable": false,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#payload_too_large",
          "request_id": "req_…"
        }
      }
    },
    {
      "code": "rate_limited",
      "http_status": 429,
      "retryable": true,
      "retry_when": "After Retry-After.",
      "meaning": "More than 10 requests per second (burst 20) on this key.",
      "fix": "Wait for the Retry-After seconds, then retry with backoff and jitter. Batch identifiers into one request instead of one request per number.",
      "doc_url": "https://mobilevalidate.com/docs/errors#rate_limited",
      "example": {
        "error": {
          "code": "rate_limited",
          "message": "…",
          "status": 429,
          "retryable": true,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#rate_limited",
          "request_id": "req_…"
        }
      }
    },
    {
      "code": "daily_cap_reached",
      "http_status": 429,
      "retryable": true,
      "retry_when": "After the daily reset (00:00 UTC).",
      "meaning": "The account's daily identifiers cap (or daily spend cap) is used up. Caps reset at 00:00 UTC.",
      "fix": "Retry after the reset shown in GET /v1/limits, or ask for a higher cap.",
      "doc_url": "https://mobilevalidate.com/docs/errors#daily_cap_reached",
      "example": {
        "error": {
          "code": "daily_cap_reached",
          "message": "…",
          "status": 429,
          "retryable": true,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#daily_cap_reached",
          "request_id": "req_…"
        }
      }
    },
    {
      "code": "spend_cap_reached",
      "http_status": 429,
      "retryable": false,
      "meaning": "The key's or account's spend cap has been reached (agent keys have a daily spend cap).",
      "fix": "Raise the cap or use another key. Retrying the same request will not help.",
      "doc_url": "https://mobilevalidate.com/docs/errors#spend_cap_reached",
      "example": {
        "error": {
          "code": "spend_cap_reached",
          "message": "…",
          "status": 429,
          "retryable": false,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#spend_cap_reached",
          "request_id": "req_…"
        }
      }
    },
    {
      "code": "internal_error",
      "http_status": 500,
      "retryable": true,
      "retry_when": "With exponential backoff.",
      "meaning": "Something failed on our side.",
      "fix": "Retry with backoff using the same Idempotency-Key so the retry is safe. If it persists, send us the request_id.",
      "doc_url": "https://mobilevalidate.com/docs/errors#internal_error",
      "example": {
        "error": {
          "code": "internal_error",
          "message": "…",
          "status": 500,
          "retryable": true,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#internal_error",
          "request_id": "req_…"
        }
      }
    },
    {
      "code": "temporarily_unavailable",
      "http_status": 503,
      "retryable": true,
      "retry_when": "After Retry-After.",
      "meaning": "The service is overloaded or every route for a check is down. A request that fails this way is not charged.",
      "fix": "Retry after Retry-After.",
      "doc_url": "https://mobilevalidate.com/docs/errors#temporarily_unavailable",
      "example": {
        "error": {
          "code": "temporarily_unavailable",
          "message": "…",
          "status": 503,
          "retryable": true,
          "param": null,
          "doc_url": "https://mobilevalidate.com/docs/errors#temporarily_unavailable",
          "request_id": "req_…"
        }
      }
    }
  ],
  "other_surfaces": [
    {
      "surface": "mcp",
      "code": "confirmation_required",
      "meaning": "A tool call would spend more than the confirmation threshold (default USD 1.00) or covers more than 100 numbers.",
      "fix": "Show the amount to the user and call again with confirm_max_cost.",
      "doc_url": "https://mobilevalidate.com/docs/mcp"
    },
    {
      "surface": "sdk",
      "code": "missing_api_key",
      "meaning": "Client-side SDK error; never returned by the API itself.",
      "fix": "See the SDK documentation.",
      "doc_url": "https://mobilevalidate.com/docs/sdk"
    },
    {
      "surface": "sdk",
      "code": "connection_error",
      "meaning": "Client-side SDK error; never returned by the API itself.",
      "fix": "See the SDK documentation.",
      "doc_url": "https://mobilevalidate.com/docs/sdk"
    },
    {
      "surface": "sdk",
      "code": "timeout",
      "meaning": "Client-side SDK error; never returned by the API itself.",
      "fix": "See the SDK documentation.",
      "doc_url": "https://mobilevalidate.com/docs/sdk"
    },
    {
      "surface": "sdk",
      "code": "invalid_response",
      "meaning": "Client-side SDK error; never returned by the API itself.",
      "fix": "See the SDK documentation.",
      "doc_url": "https://mobilevalidate.com/docs/sdk"
    },
    {
      "surface": "sdk",
      "code": "invalid_argument",
      "meaning": "Client-side SDK error; never returned by the API itself.",
      "fix": "See the SDK documentation.",
      "doc_url": "https://mobilevalidate.com/docs/sdk"
    }
  ]
}
