Skip to content

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

Read a one-time passcode to a phone by voice call

POST/api/v1/comms/voice-otp

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

  • 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)

  • 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

  • datarequired

    object

  • voiceOtpIdrequired

    string (uuid) · data.voiceOtpId

    Use with GET /comms/voice-otp/{id}
  • callIdrequired

    string · data.callId

    The call carrying the code; it also appears in GET /comms/calls and the call.completed webhook
  • statusrequired

    string · data.status

    initiated: the call is ringing. accepted: simulated on a test keyOne of initiated, accepted
  • torequired

    string · data.to

  • fromrequired

    string · data.from

    The caller ID presented
  • languagerequired

    string · data.language

  • codeLengthrequired

    integer · data.codeLength

  • repeatrequired

    integer · data.repeat

    How many times the code is read
  • code

    string · data.code

    Present only when we generated the code AND you sent returnCode: true
  • costrequired

    money | null · data.cost

    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".
  • simulated

    boolean · data.simulated

    Present and true for test keys: nothing was dialled
  • createdAtrequired

    string (date-time) · data.createdAt

    ISO-8601 timestamp (UTC)

Errors

  • 400VALIDATION_ERROR, INVALID_INPUT or BAD_REQUEST. For VALIDATION_ERROR, error.details is an array of { path, message }.
  • 401UNAUTHORIZED: missing, invalid, expired or revoked credential.
  • 403FORBIDDEN: not allowed - a scoped key lacks the scope, or the endpoint is session-only.
  • 409CONFLICT (or a code-specific 409): the change clashes with existing state.
  • 429RATE_LIMITED: slow down and retry after the Retry-After seconds.
  • 500INTERNAL_ERROR: unexpected failure. Quote X-Request-Id to support.

Get the outcome of a voice passcode call

GET/api/v1/comms/voice-otp/{id}

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

  • idrequired

    path · string (uuid)

Response 200

  • datarequired

    object

  • voiceOtpIdrequired

    string (uuid) · data.voiceOtpId

  • callIdrequired

    string | null · data.callId

  • statusrequired

    string · data.status

    One of initiated, answered, no_answer, busy, failed, accepted
  • torequired

    string · data.to

  • languagerequired

    string · data.language

  • codeLengthrequired

    integer · data.codeLength

  • costrequired

    money | null · data.cost

    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".
  • durationSecondsrequired

    integer | null · data.durationSeconds

  • createdAtrequired

    string (date-time) · data.createdAt

    ISO-8601 timestamp (UTC)
  • completedAtrequired

    string (date-time) | null · data.completedAt

    ISO-8601 timestamp (UTC)
  • simulated

    boolean · data.simulated

Errors

  • 400VALIDATION_ERROR, INVALID_INPUT or BAD_REQUEST. For VALIDATION_ERROR, error.details is an array of { path, message }.
  • 401UNAUTHORIZED: missing, invalid, expired or revoked credential.
  • 403FORBIDDEN: not allowed - a scoped key lacks the scope, or the endpoint is session-only.
  • 404NOT_FOUND: no such resource on your account.
  • 429RATE_LIMITED: slow down and retry after the Retry-After seconds.
  • 500INTERNAL_ERROR: unexpected failure. Quote X-Request-Id to support.

Send a verification code by SMS or voice call

POST/api/v1/verify/start

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

  • 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)

  • 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

  • datarequired

    object

  • verificationIdrequired

    string (uuid) · data.verificationId

    Pass to POST /verify/check
  • torequired

    string · data.to

  • channelrequired

    string · data.channel

    One of sms, voice
  • statusrequired

    string · data.status

    One of pending
  • expiresAtrequired

    string (date-time) · data.expiresAt

    ISO-8601 timestamp (UTC)
  • maxAttemptsrequired

    integer · data.maxAttempts

    Wrong guesses allowed before the verification is dead (5)
  • sendRefrequired

    string · data.sendRef

    The messageId (SMS) or callId (voice) that carried the code
  • sendStatusrequired

    string · data.sendStatus

    SMS send-time status, or initiated for a voice call
  • simulated

    boolean · data.simulated

    Present and true for test keys: nothing was sent
  • testCode

    string · data.testCode

    Test keys ONLY: the code, so a sandbox can complete the check. Never present on a live key
  • createdAtrequired

    string (date-time) · data.createdAt

    ISO-8601 timestamp (UTC)

Errors

  • 400VALIDATION_ERROR, INVALID_INPUT or BAD_REQUEST. For VALIDATION_ERROR, error.details is an array of { path, message }.
  • 401UNAUTHORIZED: missing, invalid, expired or revoked credential.
  • 403FORBIDDEN: not allowed - a scoped key lacks the scope, or the endpoint is session-only.
  • 409CONFLICT (or a code-specific 409): the change clashes with existing state.
  • 429RATE_LIMITED: slow down and retry after the Retry-After seconds.
  • 500INTERNAL_ERROR: unexpected failure. Quote X-Request-Id to support.

Check the code a user entered

POST/api/v1/verify/check

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)

  • verificationIdrequired

    string (uuid)

  • coderequired

    string

    max 32 chars

Response 200

  • datarequired

    object

  • verificationIdrequired

    string (uuid) · data.verificationId

  • statusrequired

    string · data.status

    One of approved, denied, expired, max_attempts
  • attemptsRemainingrequired

    integer · data.attemptsRemaining

  • reason

    string · data.reason

    Why a check was not approved, when it is not obvious from statusOne of wrong_code, already_used, not_pending

Errors

  • 400VALIDATION_ERROR, INVALID_INPUT or BAD_REQUEST. For VALIDATION_ERROR, error.details is an array of { path, message }.
  • 401UNAUTHORIZED: missing, invalid, expired or revoked credential.
  • 403FORBIDDEN: not allowed - a scoped key lacks the scope, or the endpoint is session-only.
  • 409CONFLICT (or a code-specific 409): the change clashes with existing state.
  • 429RATE_LIMITED: slow down and retry after the Retry-After seconds.
  • 500INTERNAL_ERROR: unexpected failure. Quote X-Request-Id to support.

Get the state of a verification

GET/api/v1/verify/{id}

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

  • idrequired

    path · string (uuid)

Response 200

  • datarequired

    object

  • verificationIdrequired

    string (uuid) · data.verificationId

  • torequired

    string · data.to

  • channelrequired

    string · data.channel

    One of sms, voice
  • statusrequired

    string · data.status

    One of pending, approved, expired, max_attempts, failed
  • attemptsrequired

    integer · data.attempts

  • maxAttemptsrequired

    integer · data.maxAttempts

  • expiresAtrequired

    string (date-time) · data.expiresAt

    ISO-8601 timestamp (UTC)
  • createdAtrequired

    string (date-time) · data.createdAt

    ISO-8601 timestamp (UTC)
  • approvedAtrequired

    string (date-time) | null · data.approvedAt

    ISO-8601 timestamp (UTC)
  • sendRefrequired

    string | null · data.sendRef

  • sendStatusrequired

    string | null · data.sendStatus

  • simulated

    boolean · data.simulated

Errors

  • 400VALIDATION_ERROR, INVALID_INPUT or BAD_REQUEST. For VALIDATION_ERROR, error.details is an array of { path, message }.
  • 401UNAUTHORIZED: missing, invalid, expired or revoked credential.
  • 403FORBIDDEN: not allowed - a scoped key lacks the scope, or the endpoint is session-only.
  • 404NOT_FOUND: no such resource on your account.
  • 429RATE_LIMITED: slow down and retry after the Retry-After seconds.
  • 500INTERNAL_ERROR: unexpected failure. Quote X-Request-Id to support.