# Create a verification passcode check

`POST /v1/verify/verifications/check`

Checks a passcode for a recipient and returns the outcome together with the verification's current state. Identify the verification by the same `to` used to create it; you do not need to store a verification ID.

A wrong or expired passcode returns `200 OK` with `success: false` and a `reason` such as `incorrect_code` or `expired`. `success: true` means the verification is complete. Each verification reports its final outcome once and cannot be checked again.

An error status is returned only when the check cannot be evaluated. A `404
E13000` means no active verification matched the recipient: either none
exists for it, or the most recent one is already resolved as verified,
expired, or out of attempts. One code covers all of those, so a `404` is not
evidence the recipient failed to verify. Treat your own record of an earlier
`success: true` as the outcome, and create a new verification only if the
recipient still needs to verify. A `422` indicates an invalid recipient. A
`429` means passcodes for a recipient are being checked too quickly.

## Code samples

**TypeScript**

```ts
const result = await bird.verify.verifications.check({
  to: { phone_number: "+15551234567" },
  code: "123456",
});
console.log(result.success);
```

Examples: [TypeScript](/docs/api/reference/create-verification-check.ts.md) · [Python](/docs/api/reference/create-verification-check.py.md) · [Go](/docs/api/reference/create-verification-check.go.md) · [PHP](/docs/api/reference/create-verification-check.php.md) · [CLI](/docs/api/reference/create-verification-check.cli.md) · [MCP](/docs/api/reference/create-verification-check.mcp.md) · [cURL](/docs/api/reference/create-verification-check.curl.md)

## Example response `200`

```json
{
  "success": false,
  "reason": "incorrect_code",
  "verification": {
    "id": "vrf_01krdgeqcxet5s7t44vh8rt9mg",
    "status": "pending",
    "reason": null,
    "to": {
      "email": "user@example.com"
    },
    "channels": [
      {
        "channel": "email"
      }
    ],
    "last_channel": "email",
    "expires_at": "2026-05-20T09:24:52Z",
    "created_at": "2026-05-20T09:14:52Z",
    "updated_at": "2026-05-20T09:14:52Z"
  },
  "attempts_remaining": 2
}
```

## Request body

- `to` (object, required): The recipient to verify. Provide an `email`, a `phone_number`, or both; at least one is required. The addresses also identify the verification: a check must supply exactly the set used on the create call, so a verification created with both addresses is not found by either one alone.
- `to.email` (string): The recipient's email address. Case does not matter; the address is lowercased before use.
- `to.phone_number` (string): The recipient's phone number in E.164 format, with the leading `+` and country code (for example `+15551234567`). A number in any other format is rejected as an invalid recipient (`422`).
- `code` (string, required): The passcode the recipient received. Passcodes are numeric; submit the digits exactly as delivered. An incorrect value is a normal `200` outcome with `success: false`. It does not return an error.

## Response body

- `success` (boolean, required): Whether the submitted passcode verified this verification. `true` means the passcode was correct and the verification is now complete; `false` means it did not verify, and `reason` says why. A verification that has already reached a final state is no longer checkable and returns `404`.
- `reason` (nullable string)

  Why the check did not succeed:

  - `incorrect_code`: The passcode was wrong and attempts remain.
  - `expired`: The validity window elapsed.
  - `attempts_exhausted`: Too many incorrect attempts were submitted.

  `null` when `success` is `true`. Treat unrecognized values as reasons added
  later.

  Possible values (may grow over time): `incorrect_code`, `expired`, `attempts_exhausted`
- `verification` (object, required)
- `verification.id` (string, required)
- `verification.status` (string, required)

  The verification's current state:

  - `pending`: Awaiting a correct passcode.
  - `verified`: A correct passcode was submitted.
  - `failed`: The verification cannot be completed. Either too many
    incorrect passcodes were submitted, or no planned channel could
    deliver one. Read `reason` to tell those apart.
  - `expired`: The validity window elapsed before a correct passcode.
  - `canceled`: The verification was canceled before completion.
  - `blocked`: A fraud or abuse control stopped the verification.

  Possible values: `pending`, `verified`, `failed`, `expired`, `canceled`, `blocked`
- `verification.reason` (nullable string): Why the verification reached its final state, or `null` while `pending` and once `verified`. See the enum for the values it can take.
- `verification.to` (object, required): The recipient to verify. Provide an `email`, a `phone_number`, or both; at least one is required. The addresses also identify the verification: a check must supply exactly the set used on the create call, so a verification created with both addresses is not found by either one alone.
- `verification.to.email` (string): The recipient's email address. Case does not matter; the address is lowercased before use.
- `verification.to.phone_number` (string): The recipient's phone number in E.164 format, with the leading `+` and country code (for example `+15551234567`). A number in any other format is rejected as an invalid recipient (`422`).
- `verification.channels` (array of object, required): The channels this verification uses to deliver the passcode, in attempt order: the first entry is tried first and later entries are fallbacks. An email recipient is verified over email; a phone recipient is verified over the phone channels enabled for its destination country, in the order that country's configuration sets.
- `verification.channels.channel` (string, required)

  The channel a passcode is delivered over. Open enum: new channels may be added over time, so treat any unrecognized value as a future channel rather than an error.

  Possible values (may grow over time): `email`, `sms`, `whatsapp`, `telegram`, `voice`
- `verification.last_channel` (nullable string)

  The channel the most recent passcode was sent on, or `null` before the first send. Open enum; new channels may be added over time, so treat any unrecognized value as a future channel rather than an error.

  Possible values (may grow over time): `email`, `sms`, `whatsapp`, `telegram`, `voice`
- `verification.metadata` (object): The key/value pairs attached when the verification was created.
- `verification.expires_at` (string, required): When the verification expires if no correct passcode is submitted first. After this time its status reports `expired`.
- `verification.verified_at` (nullable string): When the verification was completed, or `null` if it is not yet verified.
- `verification.created_at` (string, required)
- `verification.updated_at` (string, required)
- `attempts_remaining` (nullable integer): The number of check attempts left while the verification is still pending, or `null` once it has reached a final state.

## Related resources

- [Verify phone numbers at signup](/learn/series/verify-phone-numbers-at-signup) (video)
- [What does OTP mean? One-time passwords explained](/explained/verify/what-does-otp-mean) (answer)
- [Customer verification](/verify-api) (product)
- [Build your first integration](/learn/paths/integration) (course)

[Get an implementation brief](/learn/workspace?topic=verify)
