Skip to main content

Phone Number Verification

Confirm that a customer controls a phone number by sending a one-time passcode (OTP) over SMS, voice, or WhatsApp. Every verification returns carrier metadata and a risk assessment so you can detect VoIP, disposable, and high-risk numbers during onboarding.

🚀 Quick Start​

curl -X POST https://api.afriroute.ai/api/v1/identity/phone/send-otp \
-H "Authorization: Bearer $AFRIROUTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "+251911234567",
"method": "sms"
}'

📡 Endpoints​

Send OTP​

POST /v1/identity/phone/send-otp

Parameters​

FieldTypeRequiredDescription
phone_numberstringYesRecipient number in E.164 format
methodstringNosms (default), voice, or whatsapp
code_lengthintegerNoOTP length, 4–8 digits (default 6)
expiry_secondsintegerNoCode lifetime in seconds (default 300)
sender_idstringNoAlphanumeric sender ID for the SMS channel

Verify OTP​

POST /v1/identity/phone/verify-otp

Parameters​

FieldTypeRequiredDescription
otp_idstringYesThe otp_id returned by send-otp
codestringYesThe code entered by the user

💻 Code Samples​

Node.js — Send then verify
const send = await fetch('https://api.afriroute.ai/api/v1/identity/phone/send-otp', {
method: 'POST',
headers: { 'Authorization': 'Bearer $AFRIROUTE_API_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({ phone_number: '+251911234567', method: 'sms' })
});
const { otp_id } = await send.json();

// ...collect code from the user...
const verify = await fetch('https://api.afriroute.ai/api/v1/identity/phone/verify-otp', {
method: 'POST',
headers: { 'Authorization': 'Bearer $AFRIROUTE_API_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({ otp_id, code: '123456' })
});
console.log(await verify.json());
Python
import requests

base = 'https://api.afriroute.ai/api/v1/identity/phone'
headers = {'Authorization': 'Bearer $AFRIROUTE_API_KEY'}

otp_id = requests.post(f'{base}/send-otp',
headers=headers, json={'phone_number': '+251911234567', 'method': 'sms'}
).json()['otp_id']

res = requests.post(f'{base}/verify-otp',
headers=headers, json={'otp_id': otp_id, 'code': '123456'}
)
print(res.json()['verified'])

📊 Responses​

Send OTP

{
"otp_id": "otp_xyz789",
"phone_number": "+251911234567",
"method": "sms",
"sent_at": "2026-05-10T14:30:00Z",
"expires_at": "2026-05-10T14:35:00Z"
}

Verify OTP

{
"verified": true,
"phone_number": "+251911234567",
"carrier": "Ethio Telecom",
"country": "ET",
"line_type": "mobile",
"risk_assessment": {
"risk_score": 5,
"risk_level": "low",
"voip_detected": false,
"temporary_number": false,
"known_fraud": false
}
}

💡 Best Practices​

  • Rate-limit per number — cap OTP sends to roughly 3 per number per hour to control cost and abuse.
  • Use voice fallback when an SMS is not confirmed within 60 seconds.
  • Inspect risk_assessment — block or step up review when voip_detected or temporary_number is true.
  • Never log the OTP code in plaintext; store only the otp_id and verification outcome.
  • Localize the message template to the recipient's country for higher delivery rates.

⚠️ Error Handling​

CodeHTTPDescription
INVALID_PHONE400Number is not valid E.164
OTP_EXPIRED422The code has passed expires_at
OTP_INVALID422The submitted code does not match
OTP_MAX_ATTEMPTS429Too many incorrect attempts for this otp_id
UNDELIVERABLE422Carrier could not deliver to the number

See the error code reference for the full list.


Last Updated: May 2026 | Need help? [email protected]