/Errors

Safeguards

Errors

OTPRelay uses conventional HTTP status codes and returns a consistent JSON error envelope so you can branch on a stable type and code rather than parsing messages.

OTPRelay follows standard HTTP conventions: 2xx indicates success, 4xx indicates a problem with the request (a bad parameter, a missing key, a closed verification), and 5xxindicates an error on OTPRelay's side. Every failed request returns the same JSON shape, so you can handle errors uniformly across endpoints.

One case is deliberately not an HTTP error: submitting a wrong code to Check a verification returns 200 OK with verified: false. See the error codes table below.

The error object

Errors are wrapped in a top-level error object. Always branch on type and code - the message is for humans and may change.

400 Bad Request
{  "error": {    "type": "invalid_request_error",    "code": "invalid_phone_number",    "message": "The 'to' value is not a valid E.164 phone number.",    "param": "to",    "doc_url": "https://otprelay.io/docs/errors"  }}
typestringrequired
The broad category of the error, e.g. invalid_request_error. See Error types.
codestringrequired
A stable, machine-readable identifier for the specific failure, e.g. invalid_phone_number. Branch on this in code. See Error codes.
messagestringrequired
A short, human-readable description of what went wrong. For logs and debugging - do not match on its text.
paramstringoptional
Present when the error relates to a specific request field. Names the offending parameter, e.g. to.
doc_urlstringoptional
Present when there is relevant documentation. A link to the page describing this error and how to resolve it.

Error types

The type field groups errors into a handful of categories. Use it for coarse-grained handling - for example, retrying on api_error while surfacing invalid_request_error to the caller.

typewhen
invalid_request_errorThe request was malformed: a parameter is missing, the wrong type, or otherwise invalid. Fix the request before retrying.
authentication_errorThe API key is missing, malformed or expired. Returns 401. See Authentication.
rate_limit_errorYou hit a built-in limit - too many concurrent requests, or the per-verification send or check ceiling. Returns 429. See Rate limits & fraud.
delivery_errorThe code could not be delivered - the destination is blocked by Fraud Guard, or the country isn't enabled.
api_errorSomething went wrong on OTPRelay's side. Returns a 5xx status. These are rare and safe to retry with backoff.

Error codes

The code field is the value to branch on. The table below lists every code, its HTTP status, and what it means.

codeHTTPmeaning
invalid_phone_number400to is not a valid E.164 phone number.
invalid_parameter400A parameter is present but malformed or out of range.
missing_parameter400A required field is missing from the request.
authentication_failed401The API key is bad, missing or expired. See Authentication.
verification_not_found404No open verification matches the verification_id or to - it never existed or is already closed.
incorrect_code200The submitted code is wrong. Returned in the body - the HTTP status stays 200 and the verification stays pending.
max_check_attempts429More than 5 checks on this verification. It is now closed (max_attempts_reached).
max_send_attempts429More than 5 sends to this number on a single verification.
too_many_requests429A concurrency or rate limit was hit for this number. See Rate limits & fraud.
delivery_blocked402Fraud Guard blocked the destination. See Rate limits & fraud.
country_not_supported400The destination is outside OTPRelay's enabled countries. See GCC compliance & coverage.

incorrect_code is not an HTTP error

A wrong code is an expected part of the verify flow, not a failed request. The check endpoint returns 200 OK - read verified: false in the body and let the user try again until the verification is approved, expires, or hits its check limit. See Check a verification.

For the full request and response shapes that produce these errors, see Send a verification and Check a verification. Anything still undecided - including idempotency semantics and the programmable rate-limit shape - is still being finalized.