# Sending verifications

Verifying a user is two calls: [`POST /v1/verify/verifications`](/docs/api/reference/create-verification) sends a passcode to an email address or phone number, and [`POST /v1/verify/verifications/check`](/docs/api/reference/create-verification-check) submits what the user typed and tells you whether it matched. Bird generates the code, delivers it, stores only a hash, and enforces expiry and attempt limits — your app never sees or stores the code itself. Full request and response schemas live in the API reference for [creating a verification](/docs/api/reference/create-verification) and [checking a code](/docs/api/reference/create-verification-check).

## Send a code

The smallest valid request is a `to` recipient:

```bash
curl -X POST https://us1.platform.bird.com/v1/verify/verifications \
  -H "Authorization: Bearer bk_us1_..." \
  -H "Content-Type: application/json" \
  -d '{
    "to": { "phone_number": "+15551234567" }
  }'
```

Use your regional host (`https://us1.platform.bird.com` or `https://eu1.platform.bird.com`) with a matching `bk_{region}_...` key.

### Recipient

`to` names who you're verifying: an `email_address`, a `phone_number` in [E.164](https://en.wikipedia.org/wiki/E.164) format, or both. The recipient determines the delivery channels — an email address resolves to the email channel, and a phone number resolves to its phone channels (currently SMS) in the order your [country configuration](/docs/guides/verify/countries) sets for the destination country. Supplying both addresses gives Bird more than one way to reach the user: if a send on one channel fails, delivery advances to the next channel in the plan.

### Options

`options` overrides settings for this request only:

- `code_length` — passcode length for this verification, 4–8 digits, overriding the configured length.
- `channels` — reorder or narrow the delivery channels for this request. List channel names (`sms`, `email`) in the order to try them; a channel you omit is not used, and a channel not already enabled for the recipient is ignored. You can't add a channel this way — only trim or reorder what the recipient and country configuration already allow.

### Metadata

`metadata` is a free-form object echoed back on every read — use it to carry your own user ID or session reference. Settings and sender choices don't ride on the request: they come from your workspace's verification configuration, managed in the dashboard (see [Verification settings](#verification-settings)).

### The response

```json
{
  "id": "vrf_01jzp0dv4qf6vb1nc23yjc8f2e",
  "status": "pending",
  "to": { "phone_number": "+15551234567" },
  "channels": [{ "channel": "sms" }],
  "last_channel": "sms",
  "expires_at": "2026-07-09T12:24:07Z",
  "verified_at": null,
  "created_at": "2026-07-09T12:14:07Z",
  "updated_at": "2026-07-09T12:14:07Z"
}
```

`channels` is the ordered delivery plan this verification resolved to, and `last_channel` is where the most recent code went (null before the first send). `expires_at` is when the verification lapses if no correct code arrives.

The create endpoint supports [idempotent retries](/docs/guides/idempotency) via the `Idempotency-Key` header.

## Check the code

Submit whatever the user typed to [`POST /v1/verify/verifications/check`](/docs/api/reference/create-verification-check), keyed by the same recipient — no verification ID needed:

```bash
curl -X POST https://us1.platform.bird.com/v1/verify/verifications/check \
  -H "Authorization: Bearer bk_us1_..." \
  -H "Content-Type: application/json" \
  -d '{
    "to": { "phone_number": "+15551234567" },
    "code": "482913"
  }'
```

The response says whether it matched:

```json
{
  "success": false,
  "reason": "incorrect_code",
  "attempts_remaining": 4,
  "verification": { "id": "vrf_01jzp0dv4qf6vb1nc23yjc8f2e", "status": "pending", "...": "..." }
}
```

Two behaviors here are easy to miss:

- **A wrong code is a `200`, not an error.** `success: false` with a `reason` (`incorrect_code`, `expired`, `attempts_exhausted`) is a normal answer; `attempts_remaining` tells you how many tries are left. Reserve your error handling for actual request failures.
- **A verification is checkable exactly once it resolves.** Once a verification reaches a final state — verified or otherwise — further checks return `404`. Treat the first definitive answer as the answer; don't re-check to "confirm".

If the user asked for a new code, call the create endpoint again with the same recipient: the in-progress verification is reused rather than replaced. Once the resend cooldown has elapsed (60 seconds by default) a fresh code goes out; within the cooldown the call returns the live verification without sending again. Every code from the current verification that hasn't expired remains valid — the user can enter whichever one arrived.

## Statuses

A verification is `pending` until it resolves into a final state, with `reason` saying why:

| Status     | Meaning                                  | Reason               |
| ---------- | ---------------------------------------- | -------------------- |
| `verified` | A correct code arrived in time           | —                    |
| `failed`   | Too many incorrect attempts              | `attempts_exhausted` |
| `expired`  | The window elapsed before a correct code | `ttl_elapsed`        |

The status enum also reserves `canceled` and `blocked` for future use — no verification resolves to them today, but handle them as terminal if they appear. `reason` is an open enum: treat an unrecognized value as a future reason, not an error.

## Track verifications in the dashboard

The [**Verifications**](https://bird.com/dashboard/w/verify/verifications) page lists every verification the workspace created, filterable by status. Each row opens a detail view with the recipient, the resolved channel plan, the last channel used, expiry and verification times, and the metadata you attached — everything except the code itself, which is never stored or shown.

![The Verifications page listing verifications with status, recipient, channel, and creation time columns](/images/docs/dashboard-verify-verifications.png)

## Verification settings

The [**Configure**](https://bird.com/dashboard/w/verify/configure) page sets the workspace's verification cycle. Each field shows the effective value — your override where you've set one, otherwise Bird's platform default:

- **Duration** — how long a code remains valid. Default 10 minutes; 1 minute to 999 minutes.
- **Maximum Retries** — how many check attempts before the verification fails with `attempts_exhausted`. Default 5; 1–10.
- **Retry Delay** — the cooldown before a new code can be sent to the same recipient. Default 60 seconds; 0–3600.

![The Configure page's General tab with Duration, Maximum Retries, and Retry Delay fields](/images/docs/dashboard-verify-configure-general.png)

Code length isn't a field on this page: it defaults to 6 digits (4–8, numeric) and is set per request via `options.code_length`, or as a workspace default through the verification-configuration API.

## Abuse guardrails

Independent of your settings, Verify enforces platform caps to keep OTP traffic from being weaponized — against your wallet (SMS pumping) or against a victim's inbox:

- **5 sends per recipient per rolling hour** — across all your verifications to that address or number.
- **1,000 sends per workspace per day.**
- **10 checks per recipient per minute** — throttles brute-force code guessing on top of the attempt limit.

Hitting a cap returns `429`; back off and retry after the window passes. General API limits are covered in [Rate limits](/docs/guides/rate-limits).

## Next steps

| Page                                                                            | What it covers                                                        |
| ------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| [Senders & branding](/docs/guides/verify/senders)                               | What the code messages look like and how to send from your own domain |
| [Country configuration](/docs/guides/verify/countries)                          | Per-country channel order, enablement, and sender overrides           |
| [Idempotency](/docs/guides/idempotency)                                         | Safe retries with the Idempotency-Key header                          |
| [API reference: create a verification](/docs/api/reference/create-verification) | Send-endpoint schema and error details                                |
| [API reference: check a code](/docs/api/reference/create-verification-check)    | Check-endpoint schema and error details                               |