# Send and verify a code
URL: /docs/verifications/send-and-verify
LLM index: /llms.txt
Description: Send a six-digit OTP to a phone number and verify the user's code with two API calls, expiry and retry limits included.
Related: /docs/verifications/usage-patterns, /docs/sending/send-one, /docs/receiving/events, /api-reference/endpoint/verifications

# Send and verify a code

Surge's verification API sends a six-digit code to a phone number and then checks the code the user provides. It handles code generation, delivery, retry limits, and expiration. You don't manage any of that yourself.

## How it works

Verification is a two-step flow:

1. **Create a verification**: Surge sends a six-digit code to the number
2. **Check the code**: Surge validates the code the user entered

```
POST /verifications → creates the verification, sends the code
POST /verifications/{verification_id}/checks → validates the user's input
```

## 1. Send the code

<CodeGroup items={["Python","TypeScript","Ruby","Elixir","cURL"]}>

```python Python
from surge import Surge

surge = Surge()

verification = surge.verifications.create(phone_number="+18015551234")
print(verification.id)
```

```typescript TypeScript
import Surge from "@surgeapi/node";

const surge = new Surge();

const verification = await surge.verifications.create({
  phone_number: "+18015551234",
});
console.log(verification.id);
```

```ruby Ruby
require "surge_api"

surge = SurgeAPI::Client.new

verification = surge.verifications.create(phone_number: "+18015551234")
puts verification.id
```

```elixir Elixir
client = Surge.Client.new(System.get_env("SURGE_API_KEY"))

{:ok, verification} =
  Surge.Verifications.create(client, %{phone_number: "+18015551234"})

IO.puts(verification.id)
```

```bash cURL
curl -X POST https://api.surge.app/verifications \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+18015551234"
  }'
```

</CodeGroup>

```json
{
  "id": "vfn_01jrzhe8d9enptypyx360pcmxj",
  "phone_number": "+18015551234",
  "status": "pending",
  "attempt_count": 0
}
```

The user receives a text like: *"Your verification code is 847291. It expires in 10 minutes."*

## 2. Check the code

When the user submits the code in your UI, send it to Surge for verification:

<CodeGroup items={["Python","TypeScript","Ruby","Elixir","cURL"]}>

```python Python
result = surge.verifications.check("{verification_id}", code="847291")
print(result.result)  # "ok", "incorrect", "expired", etc.
```

```typescript TypeScript
const result = await surge.verifications.check("{verification_id}", {
  code: "847291",
});
console.log(result.result);
```

```ruby Ruby
result = surge.verifications.check("{verification_id}", code: "847291")
puts result.result
```

```elixir Elixir
{:ok, result} =
  Surge.Verifications.check(client, "{verification_id}", %{code: "847291"})

IO.puts(result.result)
```

```bash cURL
curl -X POST https://api.surge.app/verifications/{verification_id}/checks \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "code": "847291"
  }'
```

</CodeGroup>

The response includes a `result` field indicating whether the verification succeeded:

```json
{
  "result": "ok",
  "verification": {
    "id": "vfn_01jrzhe8d9enptypyx360pcmxj",
    "phone_number": "+18015551234",
    "status": "verified",
    "attempt_count": 1
  }
}
```

## Limits

- **Expiration:** 10 minutes from creation. After that, the verification moves to `expired` status and all check attempts return `expired`.
- **Max attempts:** 3 incorrect guesses per verification. After 3 incorrect attempts, the verification moves to `exhausted` status. Create a new verification and prompt the user to resend the code.

## Result values

| Result | Meaning | What to do |
|---|---|---|
| `ok` | Code matched | Grant access |
| `incorrect` | Wrong code | Show an error, let the user retry |
| `expired` | Code is more than 10 minutes old | Prompt user to request a new code |
| `already_verified` | This verification was already approved | Treat as `ok` (idempotent) |
| `exhausted` | 3 incorrect attempts reached | Create a new verification; do not retry the same one |

## Putting it together

A complete flow in Python:

```python
from surge import Surge

surge = Surge()

# User enters their phone number
response = surge.verifications.create(phone_number="+18015551234")
verification_id = response.id

# Later: user submits the code
check = surge.verifications.check(
    id=verification_id,
    code=user_submitted_code
)

match check.result:
    case "ok" | "already_verified":
        # Grant access
        await grant_access(user)
    case "incorrect":
        return {"error": "Incorrect code. Please try again."}
    case "expired":
        return {"error": "Code expired. Request a new one."}
    case "exhausted":
        return {"error": "Too many attempts. Please request a new code."}
```

For deliverability quirks and edge cases, see [Usage patterns](./usage-patterns).
