# One-time passcodes (OTP)

Send and verify one-time codes by SMS without writing OTP logic. We generate the code, send it as a regular SMS from the sender name OTP and check it. The code itself is never stored — only a keyed hash — and never returned to you.

## Send

```bash
curl -X POST https://smsprovider-web.vercel.app/api/v1/otp/send \
  -H "Authorization: Bearer $BATLER_API_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"to": "0501234567", "app_id": "login", "valid_minutes": 5, "text": "Your MyBrand login code: [code]"}'
```

| Field | Default | Notes |
| --- | --- | --- |
| `to` | — | Israeli mobile only |
| `app_id` | `default` | Separates flows (login, payment approval…). Each `app_id` has its own active code |
| `valid_minutes` | 5 | 1–15 |
| `max_tries` | 3 | 3–5 wrong attempts, then the code is invalidated |
| `code_length` | 6 | 4–8 digits |
| `text` | Hebrew default with `[code]` | Must contain `[code]` exactly once |

Response: `{ "session_id", "expires_at" }`. Cost: by message segments (usually one credit); verification is free. The message appears in your message history with its delivery report, with the code masked.

## Verify

```ts
const { verified, tries_left } = await sms.otp.verify({ to: "0501234567", code: userInput, app_id: "login" });
if (!verified) showError(`Wrong code, ${tries_left} attempts left`);
```

## Limits and protections

- Up to **3 codes per phone per 10 minutes** and **10 per hour** per account. Exceeding returns 429 `otp_rate_limited`.
- Do not auto-retry `verify` — every call counts as an attempt.
- Webhook events: `otp.verified`, `otp.failed`.
- Channel: SMS only.
