API Docs
Create an API key in your dashboard and send it as a Bearer token. Keys are shown once.
Authentication
Authorization: Bearer otp_live_<48 hex characters>
Send an email OTP
curl -X POST https://otpprovider.com/api/v1/email-otp/send \
-H "Authorization: Bearer $OTP_KEY" -H "Content-Type: application/json" \
-d '{"to":"user@example.com","idempotency_key":"signup-8841"}'// Node.js
const r = await fetch('https://otpprovider.com/api/v1/email-otp/send', {
method: 'POST',
headers: { Authorization: 'Bearer ' + process.env.OTP_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ to: 'user@example.com' }),
});
const { data } = await r.json(); // data.id is the request_id used to verify# Python
import os, requests
r = requests.post("https://otpprovider.com/api/v1/email-otp/send",
headers={"Authorization": "Bearer " + os.environ["OTP_KEY"]},
json={"to": "user@example.com"})
print(r.json()["data"]["id"])Endpoints
| POST | /api/v1/email-otp/send | Body: { "to": "user@example.com", "idempotency_key"?: string } |
| POST | /api/v1/email-otp/verify | Body: { "request_id": "<id from send>", "code": "123456" } |
| POST | /api/v1/whatsapp-otp/send | Body: { "to": "+201234567890", "idempotency_key"?: string }. Returns 202 (queued). |
| POST | /api/v1/whatsapp-otp/verify | Body: { "request_id": "<id>", "code": "123456" } |
| POST | /api/v1/email-validation/check | Body: { "email": "user@example.com" } |
| POST | /api/v1/email-validation/batch | Body: { "emails": [ ... up to the configured batch limit ] } |
| GET | /api/v1/account | Account summary. |
| GET | /api/v1/account/balance | Wallet balance. |
| GET | /api/v1/account/usage | Usage counts per service. |
| GET | /api/v1/account/transactions | Ledger entries. |
| GET | /api/v1/account/messages | Message history. Query: id, limit (max 200). |
Responses
// success
{ "success": true, "request_id": "req_…", "data": { … } }
// error
{ "success": false, "request_id": "req_…", "error": { "code": "INSUFFICIENT_BALANCE", "message": "…" } }| 401 | UNAUTHENTICATED / INVALID_API_KEY |
| 402 | INSUFFICIENT_BALANCE |
| 403 | ACCOUNT_NOT_ACTIVE / SERVICE_NOT_ALLOWED / KEY_SCOPE |
| 422 | VALIDATION_ERROR / INVALID_PHONE |
| 429 | RATE_LIMITED |
| 502 | Provider rejected or the outcome is uncertain: check status before retrying |
| 503 | SERVICE_DISABLED / MAINTENANCE / NO_PROVIDER |
Statuses
QUEUED, PROCESSING, SUBMITTED, SENT, DELIVERED, READ, REJECTED, FAILED, EXPIRED, UNKNOWN_OUTCOME. SUBMITTED means the provider accepted the request, not that it reached the inbox. Only states actually reported by the provider or device are shown.
Use idempotency_key so a retried request never sends or charges twice. If a request ends in UNKNOWN_OUTCOME, check GET /account/messages before sending again.