API reference
Verify
One-time passcodes by SMS or voice call: send a code, then check what the user typed. We keep only a hash of the code.
5 operationsBase URL https://packetexchange.io/api/v1Postman collection
BrowseVerify
Read a one-time passcode to a phone by voice call
POST/
- Access
- API key. Scoped keys need
voice:send. - Rate limit
- 10 requests per second
- Safe retries
- Send
X-Idempotency-Key; a replay within 24 hours returns the first response. - Real traffic
- Places a real call or sends a real message, billed to your balance. A test key simulates it against test credit.
Calls to and, when answered, reads the code one digit at a time: "Your {brand} verification code is 4, 8, 2, 9, 1, 3. Again, 4, 8, 2, 9, 1, 3." Repeated repeat times (default 2, max 3), then the call hangs up. Pass your own code (4-10 digits) or let us generate one of length digits (default 6); a generated code is returned only when you set returnCode: true. You check the code yourself; use POST /verify/start instead to have us check it. Languages: en, es, fr, de, pt, hi. brand defaults to your company name and is cleaned to letters, digits and spaces (max 30). Returns as soon as the call is ringing; the outcome and cost arrive with the call.completed webhook and on GET /comms/voice-otp/{id}. Billed exactly like POST /comms/calls: the route rate per billing increment plus the platform fee. No text-to-speech surcharge. A test key simulates the call against test credit. Omit strategy and the code goes out over Smart Routing's best_quality route for the destination (plain calls and SMS default to balanced). Limits, shared by voice passcodes and the Verify API: 5 codes per number per hour, 30 seconds between codes to the same number, a per-account daily cap, and a cap on codes into one number range per hour. A limit answers 429 with details.retryAfterSeconds. Embargoed destinations, premium-rate / satellite / high-risk ranges and numbers on your Do-Not-Contact list are refused with 400.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
X-Idempotency-Key | header | string | Any unique string (a UUID is ideal). Replays within 24 hours return the first response instead of acting twice. |
Request body (application/json)
| Field | Type | Description |
|---|---|---|
torequired | string | - |
code | string | - |
length | integer | -4 to 10 |
language | string | - |
repeat | integer | -1 to 3 |
from | string | - |
strategy | string | -One of cheapest, best_quality, balanced |
brand | string | Brand read out / sent. Cleaned to letters, digits and spaces, max 30 charactersmax 120 chars |
returnCode | boolean | - |
Response 200
| Field | Type | Description |
|---|---|---|
datarequired | object | - |
voiceOtpIdrequireddata.voiceOtpId | string (uuid) | Use with GET /comms/voice-otp/{id} |
callIdrequireddata.callId | string | The call carrying the code; it also appears in GET /comms/calls and the call.completed webhook |
statusrequireddata.status | string | initiated: the call is ringing. accepted: simulated on a test keyOne of initiated, accepted |
torequireddata.to | string | - |
fromrequireddata.from | string | The caller ID presented |
languagerequireddata.language | string | - |
codeLengthrequireddata.codeLength | integer | - |
repeatrequireddata.repeat | integer | How many times the code is read |
codedata.code | string | Present only when we generated the code AND you sent returnCode: true |
costrequireddata.cost | money | null | Test keys: the simulated charge. Live: null here; the final cost arrives with call.completed and on GET USD as a decimal string with exactly 6 places, e.g. "0.012500". |
simulateddata.simulated | boolean | Present and true for test keys: nothing was dialled |
createdAtrequireddata.createdAt | string (date-time) | ISO-8601 timestamp (UTC) |
Errors
- 400
VALIDATION_ERROR,INVALID_INPUTorBAD_REQUEST. ForVALIDATION_ERROR,error.detailsis an array of{ path, message }. - 401
UNAUTHORIZED: missing, invalid, expired or revoked credential. - 403
FORBIDDEN: not allowed - a scoped key lacks the scope, or the endpoint is session-only. - 409
CONFLICT(or a code-specific 409): the change clashes with existing state. - 429
RATE_LIMITED: slow down and retry after theRetry-Afterseconds. - 500
INTERNAL_ERROR: unexpected failure. QuoteX-Request-Idto support.
Get the outcome of a voice passcode call
GET/
- Access
- API key. Scoped keys need
voice:send. - Rate limit
- 100 requests per second (the default)
Whether the call was answered, how long it ran and what it cost. Never returns the code.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | - |
Response 200
| Field | Type | Description |
|---|---|---|
datarequired | object | - |
voiceOtpIdrequireddata.voiceOtpId | string (uuid) | - |
callIdrequireddata.callId | string | null | - |
statusrequireddata.status | string | -One of initiated, answered, no_answer, busy, failed, accepted |
torequireddata.to | string | - |
languagerequireddata.language | string | - |
codeLengthrequireddata.codeLength | integer | - |
costrequireddata.cost | money | null | What the call cost you, platform fee included. Null until it ends USD as a decimal string with exactly 6 places, e.g. "0.012500". |
durationSecondsrequireddata.durationSeconds | integer | null | - |
createdAtrequireddata.createdAt | string (date-time) | ISO-8601 timestamp (UTC) |
completedAtrequireddata.completedAt | string (date-time) | null | ISO-8601 timestamp (UTC) |
simulateddata.simulated | boolean | - |
Errors
- 400
VALIDATION_ERROR,INVALID_INPUTorBAD_REQUEST. ForVALIDATION_ERROR,error.detailsis an array of{ path, message }. - 401
UNAUTHORIZED: missing, invalid, expired or revoked credential. - 403
FORBIDDEN: not allowed - a scoped key lacks the scope, or the endpoint is session-only. - 404
NOT_FOUND: no such resource on your account. - 429
RATE_LIMITED: slow down and retry after theRetry-Afterseconds. - 500
INTERNAL_ERROR: unexpected failure. QuoteX-Request-Idto support.
Send a verification code by SMS or voice call
POST/
- Access
- API key. Scoped keys need
verify:write. - Rate limit
- 10 requests per second
- Safe retries
- Send
X-Idempotency-Key; a replay within 24 hours returns the first response. - Real traffic
- Sends a real SMS or places a real voice call carrying the code, billed as that message or call. A test key simulates it.
Generates a code of length digits (default 6), sends it over channel, and returns a verificationId. We store only a keyed hash (HMAC) of the code; the code itself is never stored or logged. The code expires after expirySeconds (default 600). SMS languages: en, es, fr, de, pt, hi, ar. Voice languages: en, es, fr, de, pt, hi. from is the SMS sender ID (defaults to your brand) or, for voice, the caller ID. Billed as the SMS or call that carries the code: the route rate plus the platform fee, no verification fee. A test key sends nothing, charges test credit and returns testCode so you can finish the flow. Omit strategy and the code goes out over Smart Routing's best_quality route for the destination (plain calls and SMS default to balanced). Limits, shared by voice passcodes and the Verify API: 5 codes per number per hour, 30 seconds between codes to the same number, a per-account daily cap, and a cap on codes into one number range per hour. A limit answers 429 with details.retryAfterSeconds. Embargoed destinations, premium-rate / satellite / high-risk ranges and numbers on your Do-Not-Contact list are refused with 400.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
X-Idempotency-Key | header | string | Any unique string (a UUID is ideal). Replays within 24 hours return the first response instead of acting twice. |
Request body (application/json)
| Field | Type | Description |
|---|---|---|
torequired | string | - |
channelrequired | string | -One of sms, voice |
length | integer | -4 to 10 |
language | string | - |
brand | string | Brand read out / sent. Cleaned to letters, digits and spaces, max 30 charactersmax 120 chars |
expirySeconds | integer | -60 to 3600 |
from | string | -max 30 chars |
strategy | string | -One of cheapest, best_quality, balanced |
Response 200
| Field | Type | Description |
|---|---|---|
datarequired | object | - |
verificationIdrequireddata.verificationId | string (uuid) | Pass to POST /verify/check |
torequireddata.to | string | - |
channelrequireddata.channel | string | -One of sms, voice |
statusrequireddata.status | string | -One of pending |
expiresAtrequireddata.expiresAt | string (date-time) | ISO-8601 timestamp (UTC) |
maxAttemptsrequireddata.maxAttempts | integer | Wrong guesses allowed before the verification is dead (5) |
sendRefrequireddata.sendRef | string | The messageId (SMS) or callId (voice) that carried the code |
sendStatusrequireddata.sendStatus | string | SMS send-time status, or initiated for a voice call |
simulateddata.simulated | boolean | Present and true for test keys: nothing was sent |
testCodedata.testCode | string | Test keys ONLY: the code, so a sandbox can complete the check. Never present on a live key |
createdAtrequireddata.createdAt | string (date-time) | ISO-8601 timestamp (UTC) |
Errors
- 400
VALIDATION_ERROR,INVALID_INPUTorBAD_REQUEST. ForVALIDATION_ERROR,error.detailsis an array of{ path, message }. - 401
UNAUTHORIZED: missing, invalid, expired or revoked credential. - 403
FORBIDDEN: not allowed - a scoped key lacks the scope, or the endpoint is session-only. - 409
CONFLICT(or a code-specific 409): the change clashes with existing state. - 429
RATE_LIMITED: slow down and retry after theRetry-Afterseconds. - 500
INTERNAL_ERROR: unexpected failure. QuoteX-Request-Idto support.
Check the code a user entered
POST/
- Access
- API key. Scoped keys need
verify:write. - Rate limit
- 20 requests per second
approved exactly once: a code is single use, and a second check of an approved verification answers denied with reason already_used. Five attempts in total; the fifth wrong guess answers max_attempts and the verification is dead. After expiresAt every check answers expired, even with the right code. Spaces and dashes in the submitted code are ignored. The comparison is constant-time.
Request body (application/json)
| Field | Type | Description |
|---|---|---|
verificationIdrequired | string (uuid) | - |
coderequired | string | -max 32 chars |
Response 200
| Field | Type | Description |
|---|---|---|
datarequired | object | - |
verificationIdrequireddata.verificationId | string (uuid) | - |
statusrequireddata.status | string | -One of approved, denied, expired, max_attempts |
attemptsRemainingrequireddata.attemptsRemaining | integer | - |
reasondata.reason | string | Why a check was not approved, when it is not obvious from statusOne of wrong_code, already_used, not_pending |
Errors
- 400
VALIDATION_ERROR,INVALID_INPUTorBAD_REQUEST. ForVALIDATION_ERROR,error.detailsis an array of{ path, message }. - 401
UNAUTHORIZED: missing, invalid, expired or revoked credential. - 403
FORBIDDEN: not allowed - a scoped key lacks the scope, or the endpoint is session-only. - 409
CONFLICT(or a code-specific 409): the change clashes with existing state. - 429
RATE_LIMITED: slow down and retry after theRetry-Afterseconds. - 500
INTERNAL_ERROR: unexpected failure. QuoteX-Request-Idto support.
Get the state of a verification
GET/
- Access
- API key. Scoped keys need
verify:write. - Rate limit
- 100 requests per second (the default)
Status, attempts used and how the code was delivered. Never returns the code.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idrequired | path | string (uuid) | - |
Response 200
| Field | Type | Description |
|---|---|---|
datarequired | object | - |
verificationIdrequireddata.verificationId | string (uuid) | - |
torequireddata.to | string | - |
channelrequireddata.channel | string | -One of sms, voice |
statusrequireddata.status | string | -One of pending, approved, expired, max_attempts, failed |
attemptsrequireddata.attempts | integer | - |
maxAttemptsrequireddata.maxAttempts | integer | - |
expiresAtrequireddata.expiresAt | string (date-time) | ISO-8601 timestamp (UTC) |
createdAtrequireddata.createdAt | string (date-time) | ISO-8601 timestamp (UTC) |
approvedAtrequireddata.approvedAt | string (date-time) | null | ISO-8601 timestamp (UTC) |
sendRefrequireddata.sendRef | string | null | - |
sendStatusrequireddata.sendStatus | string | null | - |
simulateddata.simulated | boolean | - |
Errors
- 400
VALIDATION_ERROR,INVALID_INPUTorBAD_REQUEST. ForVALIDATION_ERROR,error.detailsis an array of{ path, message }. - 401
UNAUTHORIZED: missing, invalid, expired or revoked credential. - 403
FORBIDDEN: not allowed - a scoped key lacks the scope, or the endpoint is session-only. - 404
NOT_FOUND: no such resource on your account. - 429
RATE_LIMITED: slow down and retry after theRetry-Afterseconds. - 500
INTERNAL_ERROR: unexpected failure. QuoteX-Request-Idto support.