# Errors and status codes

The Verification API reports request failures with a non-successful HTTP status and an
`errors` array. A successfully created or retrieved verification can also contain an
unsuccessful outcome through its `status`, `error_code`, and `error_detail` fields.

Use the HTTP status to identify the error category and `errors[].code` to handle a specific
request failure. For a verification returned under `data`, inspect `status` first and use
`error_code` to determine why the verification failed, expired, or was denied.

## HTTP error status codes

| Status | Applies to | When it occurs |
| --- | --- | --- |
| `400 Bad Request` | Requests with a JSON body | The top-level `data` object is missing or invalid, or the request body cannot be parsed. |
| `401 Unauthorized` | All endpoints | Credentials are missing or invalid, or the authentication mode is below the minimum configured for the OTP application. See [Authentication](../authentication.html#otp-verification-authentication). |
| `402 Payment Required` | Start a verification | The account balance is insufficient to start a verification. |
| `404 Not Found` | Status and report endpoints | No matching verification exists for the supplied identifier or number. |
| `422 Unprocessable Content` | Start and report endpoints | The JSON structure is valid, but one or more values fail validation or the submitted code cannot be accepted. |
| `429 Too Many Requests` | Start a verification | A verification for the same destination was started less than 30 seconds ago. The `Retry-After` header gives the number of seconds to wait. See [Repeated starts](start-verification.html#otp-verification-api-start-cooldown). |
| `5xx Server Error` | Any endpoint | The API or an intermediary encountered an unexpected failure. An API-generated response may contain `internal_error`; a gateway or proxy response may not use the standard error format. |

## Error response object

Every error response generated by the Verification API contains a top-level `errors` array.
A response can contain more than one error object, for example when several request fields
fail validation.

| Field | Type | Description |
| --- | --- | --- |
| `errors` | Array of objects | The request errors returned by the API. |
| `errors[].code` | `string` | Stable, machine-readable error code. Use this value in application logic. |
| `errors[].detail` | `string` | Fixed human-readable text associated with `code`. Display or log it when useful, but do not parse it or use it for branching. |

The following response contains one request validation error:

```
{
  "errors": [
    {
      "code": "delivery_method_invalid",
      "detail": "delivery method is invalid"
    }
  ]
}
```

Note

A network failure, timeout, or response generated by a proxy can occur before the
Verification API returns JSON. Handle these failures separately instead of assuming that
an `errors` array is available.

## HTTP error codes

The following tables list the error codes published for the Verification API. New codes may
be added over time. Preserve an unknown raw code and handle it using the HTTP status instead
of rejecting or failing to decode the response.

### Request validation codes

| Code | Status | Meaning | Handling |
| --- | --- | --- | --- |
| `destination_blank` | `422` | `destination` is empty or missing. | Supply a destination number. |
| `destination_invalid` | `422` | `destination` is not a valid phone number. | Correct the number before retrying. |
| `delivery_method_blank` | `422` | `delivery_method` is empty or missing. | Supply a delivery method. |
| `delivery_method_inclusion` | `422` | `delivery_method` is outside the supported list. | Use `sms` or `callout`. |
| `delivery_method_invalid` | `422` | The delivery method is invalid for the operation. On a report request, it can mean that the method does not match the verification. | Use the method selected when the verification was started. |
| `languages_invalid` | `422` | `sms.languages` or `callout.languages` contains an invalid language tag or value. A well-formed tag with no template or recording is **not** an error: it falls back to `en-US`. | Supply valid BCP 47 language tags. See [SMS languages](../verification-methods/sms.html#otp-verification-sms-languages) and [phone call languages](../verification-methods/phone-call.html#otp-verification-callout-languages). |
| `app_hash_invalid` | `422` | `sms.app_hash` is not exactly 11 characters from `A-Z`, `a-z`, `0-9`, `+`, and `/`. | Correct the Android SMS Retriever application hash or omit it. |
| `code_blank` | `422` | `code` is empty or missing for an SMS or phone-call report. | Supply the code received by the user. |
| `destination_not_supported_for_channel` | `422` | The destination cannot be verified with the selected delivery method. | Choose another supported method or destination. |

### Report submission codes

| Code | Status | Meaning | Handling |
| --- | --- | --- | --- |
| `code_invalid` | `422` | The submitted SMS or phone-call code is incorrect. | Accept another value only if the verification remains active. The report counts toward the attempt limit. |
| `already_verified` | `422` | The verification succeeded previously, but the value submitted in this request is invalid. | Do not treat this response as confirmation of the current submission. |
| `not_ready_to_report` | `422` | Challenge delivery has not reached a state in which the value can be reported. | Wait for dispatch to complete, then submit the value again. |
| `validation_failed` | `422` | The request failed a validation without a more specific code. | Inspect the other error objects and correct the request. |

Warning

`already_verified` does not verify the value supplied in the current request. Do not use
it to grant access or confirm the current user.

### Authentication, account, lookup, and rate-limit codes

| Code | Status | Meaning |
| --- | --- | --- |
| `parameter_missing` | `400` | The request body does not contain a valid top-level `data` object. |
| `unauthorized` | `401` | Authentication failed. The response is intentionally generic and does not identify which credential or account condition caused the failure. |
| `balance_insufficient` | `402` | The account balance is insufficient to start a verification. |
| `not_found` | `404` | No matching verification was found for the authenticated OTP application. |
| `destination_in_cooldown` | `429` | A verification for the same destination was started less than 30 seconds ago. Wait for the number of seconds in the `Retry-After` header before starting again. |
| `internal_error` | `422` or `5xx` | The request could not be completed because of an internal API error. |

## Verification outcome codes

When the API returns a verification object, `status` describes its current state. For a
`failed`, `expired`, or `denied` verification, `error_code` contains the
machine-readable reason and `error_detail` contains the corresponding human-readable text.
Both fields are `null` while the verification is `pending` and after it becomes
`verified`.

| `error_code` | `error_detail` | Status | Meaning | Handling |
| --- | --- | --- | --- | --- |
| `dispatch_failed` | `failed to deliver` | `failed` | The challenge could not be delivered to the destination. | Start a new verification or choose another delivery method. |
| `expired` | `expired` | `expired` | The verification lifetime elapsed before successful reporting. | Start a new verification. |
| `too_many_attempts` | `too many attempts` | `failed` | An incorrect code was reported too many times. | Start a new verification. Do not submit another value to this verification. |
| `stale_dispatch` | `number unreachable` | `failed` | The destination could not be reached during challenge delivery. | Check the number or choose another delivery method. |
| `application_deleted` | `application deleted` | `failed` | The OTP application was deleted while the verification was in progress. | Use an active OTP application and start a new verification. |
| `superseded` | `superseded` | `failed` | A newer verification for the same application and number replaced this one. | Continue with the newer verification. |
| `denied_missing_callback_url` | `application has no callback_url` | `denied` | The start request used `public` authentication, but the OTP application has no callback URL configured. | Configure a callback URL before starting another verification. |
| `denied_by_callback` | `your callback denied the request` | `denied` | The [request callback](../callbacks.html#otp-verification-callbacks) returned `deny`. | Review the authorization decision made by your backend. |
| `denied_invalid_callback_response` | `callback response was invalid` | `denied` | The callback timed out or returned an unusable response, such as a non-`2xx` status, invalid JSON, unsupported action, or body larger than 8 KB. | Correct the callback endpoint before starting another verification. |

`too_many_attempts` can first appear as `errors[].code` when a report is rejected. A
later status request returns the completed verification with `status` `failed` and
`error_code` `too_many_attempts`.

## Handling errors safely

1. Check the HTTP status to determine whether the API returned a successful response or an
   error response.
2. For a non-successful response, inspect every object in `errors` and branch on `code`.
3. For a verification returned under `data`, inspect `status` and then `error_code`.
4. Preserve unknown codes and handle them using the HTTP status or verification status. Do
   not fail response decoding only because a new code is introduced.
5. Use `detail` and `error_detail` for display or logging only. Do not parse them or use
   them as application logic.

Warning

A report request can consume one of the allowed attempts. If a timeout or connection
failure makes the result uncertain, retrieve the verification status before submitting
the code again.

On this page
