2FA API
Two-factor sign-inin two API calls
Call start when a user signs in and check with the digits they type. You never store the code, and you pay only for the message or call. The Verify API makes the code, delivers it by text or by a call that reads it aloud, and answers approved or denied.
2 routes serve +13125887000, cheapest first
United States (All Operators)
1/1 billing
$0.0092/msg
United States (All Operators)
1/1 billing
$0.02813/msg
- Text or a spoken call
- Hashed, single-use codes
- Test keys return the code
Everything two-factor needs, nothing to maintain
No code generator, no code table, no retry bookkeeping. Two endpoints carry the whole second factor, and the edge cases are handled on our side.

Two requests, no state on your side
Start returns a verification ID; check returns approved, denied, expired or max_attempts. Codes are 4 to 10 digits (6 by default), single use, and valid for 10 minutes unless you set 1 to 60.
A second factor for every user
Users on a landline, abroad or unable to read a text get the code by a call that reads it twice, in English, Spanish, French, German, Portuguese or Hindi.
Your brand on the code
Pass your brand and it is written into the text and spoken on the call. SMS codes go from it as an alphanumeric sender unless you pass your own.
The whole flow in CI
On a test key the start is routed and priced on a real route, nothing is delivered, and the reply carries the code as testCode so your tests can complete the check.
Guessing and resend storms bounded
5 attempts per code, a 30-second resend cooldown per number, and a 429 with retryAfterSeconds your sign-in screen can show as a countdown.
The second factor in three steps
Your user passes the password step
Your server calls POST /verify/start with their number and a channel, sms or voice, plus your brand and language if you like.
The code reaches them
As a text, or as a call from the caller ID you choose that reads the digits aloud.
Check and let them in
POST /verify/check with the verification ID and what they typed. On approved, finish the sign-in.
# 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": "voice",
"from": "+14155550199",
"language": "es"
}'
# 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: 'voice', from: '+14155550199', language: 'es' },
{ '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": "voice", "from": "+14155550199", "language": "es"},
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"])Voice channel shown, in Spanish. Change channel to sms for a text. Test keys return testCode instead of calling (sandbox credit is added with an invite code).
A real market, in the open
Nothing worth stealing
Only an HMAC hash of each code is kept, and a code dies after five wrong guesses or its expiry.
Refused before routing
Premium-rate and high-risk ranges, embargoed destinations and your do-not-contact list are refused before anything is sent or billed.
Fails closed
If the limit store cannot be reached, the API refuses to send codes rather than send them without limits.
One key, one job
Scope a key to the verify permission and it can start and check codes and nothing else.
Live interconnects through Minutes Network
2FA API, answered
Anything else is in the help centre, or ask the team directly.
When should I use the voice channel instead of SMS?
Offer it as the second option on your code screen: for landlines, for users travelling without text roaming, and for anyone who prefers to hear the code. Start again with channel voice; the same check request completes it.
How long is a code valid, and how many tries does a user get?
Ten minutes by default, or anything from 1 to 60 minutes you set with expirySeconds. Five wrong attempts end the verification with max_attempts, and a used code cannot be checked again.
Do I have to store the code or compare it myself?
No. We generate the code, keep only its hash and do the comparison. You keep the verification ID for the few minutes between start and check.
What happens when a user taps resend too quickly?
A second code to the same number within 30 seconds is refused with a 429 that says how many seconds to wait, so your screen can show a countdown instead of an error.
Can the message and the call carry my brand?
Yes. Pass brand (up to 30 letters, digits and spaces) and it is used in the text and spoken on the call. Anything else in it is stripped, so a brand can never smuggle a link into a code message.
What does two-factor cost per sign-in?
One short SMS or one short call, at the route rate plus 2% capped at $0.001. There is no monthly fee and no fee per verification.
Ship two-factor sign-in
Free account and API key. Build it against a test key, then switch keys to go live. Prepaid from $5.