Two endpoints do the work. Everything else is optional.
/start with a phone number, keep the returned id./check with that id and whatever the user typed.# 1. Send the code
curl https://api.zameel.co/api/v1/verify/start \
-H "Authorization: Bearer $ZAMEEL_AUTH_KEY" \
-H "Content-Type: application/json" \
-d '{ "to": "+966501234567" }'
# { "data": { "id": "b9c1…", "status": "pending" } }
# 2. Check what the user typed
curl https://api.zameel.co/api/v1/verify/check \
-H "Authorization: Bearer $ZAMEEL_AUTH_KEY" \
-H "Content-Type: application/json" \
-d '{ "verification_id": "b9c1…", "code": "402913" }'
# { "data": { "status": "verified", "verified": true } }Every request carries your key as a bearer token. Keys look like zauth_live_… or zauth_test_…, and only a hash of each key is stored, so if you lose one, revoke it and create another.
Authorization: Bearer zauth_live_aeff4bfac44643ea734ba9f510593b98Keys belong on your server. A key in a mobile app or browser bundle can be extracted and used to spend your balance.
/api/v1/verify/startGenerates a code and delivers it over WhatsApp. The code is never in the response. That is the point.
| Field | Type | Notes |
|---|---|---|
to | string, required | Destination in E.164, e.g. +966501234567 |
language | string, optional | Overrides the application default (ar / en) |
metadata | object, optional | Echoed back on the verification record |
{
"success": true,
"data": {
"id": "b9c1e0d2-…",
"status": "pending",
"to": "+966*****4567",
"country": "966",
"attempts": 0,
"delivery_status": "sent",
"expires_at": "2026-09-03T12:05:00.000Z"
}
}/api/v1/verify/checkConsumes one attempt. A correct code is burned immediately, so the same code can never be replayed.
| Field | Type | Notes |
|---|---|---|
verification_id | string, required | The id returned by /start |
code | string, required | 4–10 digits, as the user typed it |
{
"success": true,
"data": { "status": "verified", "verified": true, "attempts": 1 }
}A wrong code returns 400 INCORRECT_CODE with attempts_remaining. After the last attempt the code is destroyed and further checks return 410 CODE_EXPIRED, so request a new one.
/api/v1/verify/:idCurrent state of a verification, without consuming an attempt.
/api/v1/verify/:id/cancelAbandons a pending verification, e.g. when the user edits their number.
/api/v1/verify/usageYour current hourly and daily consumption and remaining credit.
Failures return success: false, a human-readable message, and a stable errors.code. Branch on the code, not the message.
| Code | HTTP | Meaning |
|---|---|---|
INVALID_PHONE_NUMBER | 400 | The destination is not valid E.164. |
UNKNOWN_COUNTRY | 400 | The dial code could not be resolved. |
INVALID_CODE_FORMAT | 400 | The submitted code is not 4–10 digits. |
INCORRECT_CODE | 400 | Wrong code. `attempts_remaining` is included. |
APP_INACTIVE | 403 | The application is paused. |
COUNTRY_NOT_ALLOWED | 403 | That country is not enabled for this application. |
INSUFFICIENT_CREDIT | 402 | Prepaid balance is exhausted. |
VERIFICATION_NOT_FOUND | 404 | No such verification for this application. |
VERIFICATION_NOT_PENDING | 409 | Already verified, failed, or cancelled. |
CODE_EXPIRED | 410 | The code passed its lifetime. |
RESEND_TOO_SOON | 429 | Cooldown active. `retry_after` is in seconds. |
NUMBER_HOURLY_LIMIT | 429 | Per-destination hourly limit reached. |
NUMBER_DAILY_LIMIT | 429 | Per-destination daily limit reached. |
APP_HOURLY_QUOTA | 429 | Application-wide hourly quota exhausted. |
APP_DAILY_QUOTA | 429 | Application-wide daily quota exhausted. |
TOO_MANY_ATTEMPTS | 429 | Wrong-guess limit hit; the code is burned. |
DELIVERY_FAILED | 502 | WhatsApp rejected the message. |
NO_SENDER_AVAILABLE | 503 | No healthy sender. Retry shortly. |
A zauth_test_ key runs the identical path: the same rate limits, the same attempt lockout, the same records in your log, but nothing reaches WhatsApp and nothing is billed. Every test verification accepts the code 000000.
Test keys also ignore the country allowlist and your credit balance, so you can integrate fully before enabling live sending.