> ## Documentation Index
> Fetch the complete documentation index at: https://docs.robase.dev/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> For chatbots and coding agents, start at /guides/ai-agents.
> Hosted MCP: POST https://api.robase.dev/mcp (Streamable HTTP). Auth: Authorization: Bearer robe_… or robe_agent_….
> Phase 1 tools: get_pricing, send_otp, verify_otp, get_otp, send_sms, get_sms. No payment or top-up tools.
> Call get_pricing; treat listed:true as marketed markets (NG, GH, KE, ZA, GB, BJ, CI, US). Do not invent countries or delivery percentages.
> Prefer OTP-shaped tests such as "Your Robase test OTP is 123456". Do not send Hello or Good morning as tests. Match errors on error.type.

# OTP verification

> Generate, deliver, and verify a one-time passcode over SMS or WhatsApp.

Robase generates the code, sends it over SMS and/or WhatsApp, and checks what the user types. You store the `id` from send — never the code.

## Delivery modes

Each workspace has an `otp_delivery_mode`:

| Mode | Behaviour |
| - | - |
| `sms` | SMS only (default). `channel=whatsapp` → `400 channel_not_allowed`. |
| `whatsapp` | WhatsApp only. `channel=sms` refused. |
| `both` | One code, SMS **and** WhatsApp. Explicit `channel` refused. Both legs charged. |
| `either` | Per-request `channel`; omit means SMS. |

WhatsApp OTP uses a Meta **Authentication** template with a **copy-code** button. It is OTP delivery only — not general WhatsApp messaging.

**No silent fallback.** A failed WhatsApp leg is refunded and reported failed; it is never re-sent as SMS unless the mode is `both` (two intentional legs) or the caller retries on a new send.

## Send

```bash theme={null}
curl -X POST https://api.robase.dev/v1/otp/send \
  -H "Authorization: Bearer robe_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "+2348012345678",
    "code_length": 6,
    "ttl_seconds": 600,
    "language": "fr",
    "channel": "whatsapp"
  }'
```

| Field | Default | Notes |
| - | - | - |
| `phone_number` | required | E.164 |
| `code_length` | 6 | Integer 4–8. Omitting the field is not the same as sending `0`. |
| `ttl_seconds` | 600 | 1–3600 |
| `language` | workspace default | `en` or `fr` for the SMS body (WhatsApp uses the approved template language) |
| `channel` | mode-dependent | `sms` or `whatsapp` when the mode allows it |
| `metadata` | `{}` | Returned on GET |

`Accept-Language` changes error prose only, not the message body.

Response `status` is always `pending` on success. The code is never returned. `channel` on the response reports which leg(s) were queued.

## Verify

```bash theme={null}
curl -X POST https://api.robase.dev/v1/otp/verify \
  -H "Authorization: Bearer robe_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"otp_id":"550e8400-e29b-41d4-a716-446655440000","code":"123456"}'
```

Comparison is constant-time. Verify is the same `otp_id` regardless of whether the user read the code from SMS or WhatsApp.

| Result | HTTP | Body |
| - | - | - |
| Match | 200 | `valid: true`, `status: verified` |
| Wrong, attempts left | 200 | `valid: false`, `attempts_remaining` |
| Expired / already verified / budget gone | 409 | `otp_expired`, `otp_already_verified`, or `max_attempts_exceeded` |

GET `/v1/otp/{id}` returns status, lengths, and timestamps — never the code.

## Rate limits

3 sends / 10 minutes and 10 verifies / 10 minutes per phone number.

## Credits

SMS OTP uses the destination SMS rate from `GET /v1/pricing`. WhatsApp OTP uses the live `whatsapp` array on the same endpoint: `ceil(cost_ngn * 1.20 / credit_price)`, minimum 1, from `whatsapp_otp_costs` + FX.

## Delivery

Use `otp.sent` / `otp.delivered` / `otp.failed` webhooks or poll GET. Credits refund if a queued leg fails. Nigeria OTP SMS uses transactional routes so DND is not treated as a promo filter — [DND](/guides/dnd-compliance).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.