Verify API · SMS and voice OTP
One-time codes, no verification fee
The Verify API makes the code, sends it by text or reads it aloud on a call, and checks what your user types. You pay for the message or call that carried it, and nothing per verification.
- Two requests: start and check
- Voice codes in six languages
- Codes kept only as a hash
Live from the marketplace
Price the exact number you will dial
Type any number in international format. Every live route that serves it comes back with its rate, billing increment and caller ID type, cheapest first.
3 routes serve +447911123456, cheapest first
United Kingdom (All Operators)
1/1 billing
$0.02025/msg
United Kingdom (All Operators)
1/1 billing
$0.0396/msg
United Kingdom (All networks)
60/60 billing
$0.0650/msg
Limits built in
Pumping attacks hit a wall before they hit your balance
30s
Resend cooldown
between codes to one number
5an hour
Per number
codes to any one number
20an hour
Per number range
catches walks through a block
5
Guesses per code
then the code is dead
1,000a day
Per account
raised on request
Defaults on every account. Premium-rate, satellite and known pumping ranges are refused before any code is sent, and so is any number on your do-not-contact list. If the limit store cannot be reached, the API refuses to send rather than send without limits.
Why verify here
Pay for the message, not the verification
A code is one short message or one short call. That is all it costs here, and the limits that stop it being abused are already switched on.

No per-verification fee
A code costs the SMS segment or the billed seconds of the call that carried it, plus the platform fee of 2% capped at $0.001. Nothing per verification and nothing extra for text-to-speech.
Limits against SMS pumping
A 30-second resend cooldown, 5 codes per number an hour, 20 per number range an hour and a daily cap per account. Premium-rate, satellite and known pumping ranges are refused outright.
A voice channel when SMS will not do
Set channel to voice and a call reads the digits aloud, twice, in English, Spanish, French, German, Portuguese or Hindi, from the caller ID you pass.
A code you never have to store
We generate it, keep only an HMAC hash, allow 5 attempts and expire it after 10 minutes, or anything from 1 to 60 minutes that you set.
Call-in: no SMS to pump
Your user calls your own phone number, bought in the DID Store in their country, instead of typing a code. With no text message, there is nothing to pump, filter or block, and no message fees: the number's monthly price plus $0.01 per approved verification, nothing if it expires, and the call is free for the caller.
A prepaid ceiling
Your balance is the hard stop: an attack cannot spend money you have not topped up. Add a daily spend alert and an auto-recharge with a daily cap.
How it works
Verify a number in two requests
- Step 1
Start
POST /verify/start with the number and a channel, sms or voice. We generate the code, send it and return a verification ID. - Step 2
Your user types the code
It arrives as a text in their language, or as a call that reads it out twice. - Step 3
Check
POST /verify/check with the ID and the digits. The answer is approved, denied, expired or max_attempts, and a wrong code is an answer, not an error.
# 1. Send the code. We generate it and keep only a hash.
curl -X POST https://packetexchange.io/api/v1/verify/start \
-H "Authorization: Bearer $PACKETEXCHANGE_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: $(uuidgen)" \
-d '{
"to": "+14155550100",
"channel": "sms"
}'
# 2. Check what the user typed.
curl -X POST https://packetexchange.io/api/v1/verify/check \
-H "Authorization: Bearer $PACKETEXCHANGE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "verificationId": "<from step 1>", "code": "482913" }'import { randomUUID } from 'node:crypto';
const px = (path, body, headers = {}) =>
fetch(`https://packetexchange.io/api/v1${path}`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PACKETEXCHANGE_API_KEY}`,
'Content-Type': 'application/json',
...headers,
},
body: JSON.stringify(body),
}).then((r) => r.json());
// 1. Send the code. We generate it and keep only a hash.
const start = await px(
'/verify/start',
{ to: '+14155550100', channel: 'sms' },
{ 'X-Idempotency-Key': randomUUID() },
);
// 2. Check what the user typed: approved, denied, expired or max_attempts.
const check = await px('/verify/check', {
verificationId: start.data.verificationId,
code: userInput, // the digits your user typed
});
console.log(check.data.status);import os, uuid
import httpx
px = httpx.Client(
base_url="https://packetexchange.io/api/v1",
headers={"Authorization": f"Bearer {os.environ['PACKETEXCHANGE_API_KEY']}"},
)
# 1. Send the code. We generate it and keep only a hash.
start = px.post(
"/verify/start",
json={"to": "+14155550100", "channel": "sms"},
headers={"X-Idempotency-Key": str(uuid.uuid4())},
).json()["data"]
# 2. Check what the user typed: approved, denied, expired or max_attempts.
check = px.post(
"/verify/check",
json={"verificationId": start["verificationId"], "code": user_input}, # what they typed
).json()["data"]
print(check["status"])A test key returns the code as testCode, so the whole flow runs in CI without a phone (sandbox credit is added with an invite code).
One request with your key. The same call works from any language, and every endpoint is documented with its fields and errors.
Every endpoint in the API reference3,062routes live on the marketplace now
The market, live
213 destinations with a live route. Prepaid from $5, platform fee 2% capped at $0.001.
- Platform fee
- 2%, capped at $0.001
- Top up from
- $5, card or crypto
Fails closed
If the limit store cannot be reached, the API refuses to send codes rather than send them without limits.
Refused before routing
Embargoed destinations, high-risk ranges and numbers on your do-not-contact list are refused before anything is sent or billed.
Every code accounted for
The cost of each send is recorded against its verification, so finance can reconcile codes to the cent.
Scoped keys
A key limited to the verify permission can start and check codes and do nothing else.
Questions
OTP API, answered
Anything else is in the help centre, or ask the team directly.
What does one code cost?
By SMS, the route's per-segment rate for the destination plus 2%, capped at $0.001; a code message fits in one segment. By voice, the route's per-minute rate for the billed seconds of a short call, plus the same capped fee. There is no fee per verification.
Is there a fee per verification or per successful check?
Not for codes. Starting and checking a code verification costs nothing in itself; you pay only for the SMS or call that delivered the code. Call-in verification is the one flat price: $0.01 per approved verification on top of your phone number's monthly price, with no message or call to pay for, and nothing if it expires.
What is call-in verification?
Instead of sending a code, your user calls one of your own phone numbers from the phone being verified. Buy a number in the DID Store in their country, switch on call-in for it, then start with channel call_in and show the number we return. The call is matched by caller ID and ended without being answered, so it is free for them. It works in any country where you hold one of our phone numbers. A caller ID is weaker proof than a code, so use call-in for sign-up and phone number confirmation, and a code for account recovery or payments.
How does the API stop SMS pumping?
Premium-rate, satellite and known pumping ranges are refused before any send. Codes are capped per number and per number range each hour, with a resend cooldown and a daily account cap. Your prepaid balance is the final ceiling.
Which languages are supported?
Voice codes are spoken in English, Spanish, French, German, Portuguese and Hindi. SMS codes can also be sent in Arabic.
Does it fall back from SMS to voice on its own?
No, you decide. If a carrier refuses the SMS, the start request returns an error that suggests the voice channel, and the refused attempt does not use up the hourly limit. Start again with channel voice.
Which route carries the code?
Verify picks the best-quality route for the number by default. Pass strategy cheapest or balanced to change that.
Can I send my own code instead?
Yes. Send it in your own words with POST /comms/sms, or have a call speak it with POST /comms/voice-otp. The price is the same: the message or the call.
Add phone verification in two requests
Free account and API key. Prepaid from $5, and every code costs only the message or call that carries it.
