Skip to content

API reference

Voice and SMS

Place single calls and send single SMS over the routes you bought, and read their history.

7 operationsBase URL https://packetexchange.io/api/v1Postman collection

List your API call history

GET/api/v1/comms/calls

Access
API key. Full-access keys only; not reachable by scoped keys.
Rate limit
100 requests per second (the default)

Ledger entries for calls placed through POST /comms/calls, newest first.

Parameters

  • type

    query · string

    Accepted but not applied: each endpoint already fixes its own typeOne of api_call, api_sms, smpp_sms, dialer_campaign
  • from

    query · string (date-time)

    Only rows created at or after this instant
  • to

    query · string (date-time)

    Only rows created before this instant
  • cursor

    query · string (uuid)

    The nextCursor from the previous page
  • limit

    query · integer

    Default 25

Response 200

  • datarequired

    object[]

  • idrequired

    string (uuid) · data[].id

  • userIdrequired

    string (uuid) · data[].userId

  • typerequired

    string · data[].type

    Ledger entry type, e.g. charge
  • amountrequired

    money · data[].amount

    Signed: a charge is negative USD as a decimal string with exactly 6 places, e.g. "0.012500".
  • balanceAfterrequired

    money · data[].balanceAfter

    USD as a decimal string with exactly 6 places, e.g. "0.012500".
  • referencerequired

    string | null · data[].reference

    Human-readable description of the call or message
  • relatedEntityTyperequired

    string | null · data[].relatedEntityType

    api_call for calls; api_sms or smpp_sms for messages
  • relatedEntityIdrequired

    string (uuid) | null · data[].relatedEntityId

    The callId or messageId
  • callIdrequired

    string | null · data[].callId

  • createdAtrequired

    string (date-time) · data[].createdAt

    ISO-8601 timestamp (UTC)
  • nextCursorrequired

    string | null

    Pass as cursor to fetch the next page; null on the last page.
  • hasMorerequired

    boolean

    True when another page exists.

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.
  • 429RATE_LIMITED: slow down and retry after the Retry-After seconds.
  • 500INTERNAL_ERROR: unexpected failure. Quote X-Request-Id to support.

Place a single outbound call

POST/api/v1/comms/calls

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.

Dials to over a route you purchased (or one Smart Routing selects). By default the request BLOCKS until the call ends - answer and hangup, no answer, or maxDuration - then returns the outcome and what it cost (200); set a client timeout above maxDuration. With async: true it returns at once with 202 and status: ringing; follow the call on GET /comms/calls/{id} or with the call.ringing, call.answered, call.gathered and call.completed webhooks. Validation, routing and balance errors are still returned straight away in both modes. actions make the answered call speak (say, in en, es, fr, de, pt or hi), play an MP3 (play), collect keypad digits (gather), wait (pause) or end (hangup), in order; audio is prepared before dialling, so a bad URL costs nothing. Billed per the route increment at the route price plus the platform fee, after the call completes; there is no charge for spoken text. A test key simulates the call against test credit and runs no actions.

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

    Destination number (E.164)
  • fromrequired

    string

    Caller ID to present (E.164)
  • routeId

    string (uuid)

    A route you purchased. Omit to let Smart Routing pick one
  • strategy

    string

    Smart Routing strategy used when routeId is omittedOne of cheapest, best_quality, balanced
  • maxDuration

    integer

    Hang up after this many seconds10 to 3600, default 300
  • async

    boolean

    Return at once (202, status ringing) instead of waiting for the call to end. Follow the call on GET /comms/calls/{id} or with the call.* webhooks
  • actions

    object[]

    What the call does once answered, in order: say, play, gather, pause, hangup. At most 10 actions, 1,500 characters of spoken text, 3 play URLs and 5 gathers. The call ends when the actions finish
  • say

    string · actions[].say

    Text spoken by our text-to-speech voicemax 500 chars
  • language

    string · actions[].language

    Overrides the call default for this actionOne of en, es, fr, de, pt, hi
  • play

    string (uri) · actions[].play

    HTTPS URL of an MP3 file, at most 2 MB. We download it once before dialling (public addresses only, no redirects) and play our copymax 2048 chars
  • gather

    object · actions[].gather

  • digits

    integer · actions[].gather.digits

    Most keys to collect1 to 20, default 1
  • timeout

    integer · actions[].gather.timeout

    Seconds to wait for the first key1 to 30, default 5
  • finishOnKey

    string · actions[].gather.finishOnKey

    Key that ends input early; empty for nonedefault "#"One of #, *,
  • tries

    integer · actions[].gather.tries

    Prompt and wait again this many times in total when nothing is pressed1 to 3, default 1
  • say

    string · actions[].gather.say

    Prompt spoken while listening; a key press interrupts itmax 500 chars
  • play

    string (uri) · actions[].gather.play

    Prompt played while listening (instead of say)max 2048 chars
  • language

    string · actions[].gather.language

    One of en, es, fr, de, pt, hi
  • pause

    integer · actions[].pause

    Seconds of silence1 to 10
  • hangup

    boolean · actions[].hangup

    One of true
  • language

    string

    Default language for say actions (default en)One of en, es, fr, de, pt, hi

Response 200

  • datarequired

    object

  • callId

    string (uuid) · data.callId

    Use with GET /comms/calls/{id}; the same id appears on the call.* webhooks
  • to

    string · data.to

  • from

    string · data.from

  • status

    string · data.status

    The call has been handed to the network and is being dialledOne of ringing
  • sipResponseCode

    integer | null · data.sipResponseCode

  • hangupCause

    string | null · data.hangupCause

  • durationSeconds

    integer · data.durationSeconds

  • billableSeconds

    integer · data.billableSeconds

    Duration rounded up to the route billing increment
  • cost

    money · data.cost

    What this call cost you, platform fee included USD as a decimal string with exactly 6 places, e.g. "0.012500".
  • billingIncrement

    string · data.billingIncrement

    The route billing increment, e.g. "6/6" or "60/60"
  • startedAt

    string (date-time) · data.startedAt

    ISO-8601 timestamp (UTC)
  • completedAt

    string (date-time) · data.completedAt

    ISO-8601 timestamp (UTC)
  • simulated

    boolean · data.simulated

    Present and true for test-key calls: nothing was dialled
  • routeId

    string (uuid) | null · data.routeId

    Present on calls placed over a failover chain (Smart Routing calls with automatic failover, and Switch sub-account calls): the route that carried the call, or the last one tried
  • attempts

    object[] · data.attempts

    Present when the call was tried on more than one route: each route tried, in order. Only the one that connected is billed; a wrong number (404/484), a busy or declined call and no answer are never retried
  • routeIdrequired

    string (uuid) · data.attempts[].routeId

  • sipResponseCoderequired

    integer | null · data.attempts[].sipResponseCode

  • statusrequired

    string · data.attempts[].status

  • hangupCauserequired

    string | null · data.attempts[].hangupCause

  • mode

    string · data.mode

    One of async
  • actions

    integer · data.actions

    How many call actions will run on answer
  • statusUrl

    string · data.statusUrl

    Path of GET /comms/calls/{id} for this call

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 one call: live status, cost and gathered digits

GET/api/v1/comms/calls/{id}

Access
API key. Scoped keys need voice:send.
Rate limit
100 requests per second (the default)

Works for any call placed through POST /comms/calls, while it runs and after it ends: status, timestamps, duration, cost, the hangup cause in plain words, and the digits collected by gather actions. A reseller sub-account key sees only its own calls. Unknown ids answer 404.

Parameters

  • idrequired

    path · string (uuid)

Response 200

  • datarequired

    object

  • callIdrequired

    string (uuid) · data.callId

  • statusrequired

    string · data.status

    queued, ringing and answered are live states; completed (answered, then ended), no_answer, busy and failed are finalOne of queued, ringing, answered, completed, no_answer, busy, failed
  • moderequired

    string · data.mode

    One of sync, async
  • torequired

    string · data.to

  • fromrequired

    string · data.from

  • simulatedrequired

    boolean · data.simulated

    True for test-key calls: nothing was dialled
  • createdAtrequired

    string (date-time) · data.createdAt

    ISO-8601 timestamp (UTC)
  • ringingAtrequired

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

    ISO-8601 timestamp (UTC)
  • answeredAtrequired

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

    ISO-8601 timestamp (UTC)
  • endedAtrequired

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

    ISO-8601 timestamp (UTC)
  • durationSecondsrequired

    integer | null · data.durationSeconds

  • billableSecondsrequired

    integer | null · data.billableSeconds

    Duration rounded up to the route billing increment
  • 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".
  • billingIncrementrequired

    string | null · data.billingIncrement

  • sipResponseCoderequired

    integer | null · data.sipResponseCode

  • hangupCauserequired

    string | null · data.hangupCause

    The switch cause code, e.g. NORMAL_CLEARING or USER_BUSY
  • hangupReasonrequired

    string | null · data.hangupReason

    Why the call ended, in plain words. Null while it is live
  • errorrequired

    string | null · data.error

    Why the call could not be placed, when it failed before reaching the network
  • actionsrequired

    any[] | null · data.actions

    The call actions as you sent them
  • gatheredrequired

    object[] | null · data.gathered

    Keypad input from gather actions, filled in when the call ends
  • indexrequired

    integer · data.gathered[].index

    Which gather action (0 for the first)
  • digitsrequired

    string | null · data.gathered[].digits

    The keys pressed, without the finish key
  • statusrequired

    string · data.gathered[].status

    no_input: nothing was pressed, or the call ended before this stepOne of received, no_input
  • routeId

    string (uuid) | null · data.routeId

    Present when the call was placed over a failover chain: the route that carried it, or the last one tried
  • attempts

    object[] · data.attempts

    Present when the call was tried on more than one route: every attempt in order. Only the attempt that connected is billed
  • attemptrequired

    integer · data.attempts[].attempt

    1 for the first route tried
  • routeIdrequired

    string (uuid) | null · data.attempts[].routeId

  • statusrequired

    string · data.attempts[].status

    answered, failed, busy or no_answer
  • sipResponseCoderequired

    integer | null · data.attempts[].sipResponseCode

  • hangupCauserequired

    string | null · data.attempts[].hangupCause

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.
  • AlsoNOT_FOUND

List your SMS history

GET/api/v1/comms/sms

Access
API key. Full-access keys only; not reachable by scoped keys.
Rate limit
100 requests per second (the default)

Ledger entries for messages sent over the API and SMPP, newest first.

Parameters

  • type

    query · string

    Accepted but not applied: each endpoint already fixes its own typeOne of api_call, api_sms, smpp_sms, dialer_campaign
  • from

    query · string (date-time)

    Only rows created at or after this instant
  • to

    query · string (date-time)

    Only rows created before this instant
  • cursor

    query · string (uuid)

    The nextCursor from the previous page
  • limit

    query · integer

    Default 25

Response 200

  • datarequired

    object[]

    Same fields as CommsHistoryEntry, shown earlier on this page.
  • nextCursorrequired

    string | null

    Pass as cursor to fetch the next page; null on the last page.
  • hasMorerequired

    boolean

    True when another page exists.

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.
  • 429RATE_LIMITED: slow down and retry after the Retry-After seconds.
  • 500INTERNAL_ERROR: unexpected failure. Quote X-Request-Id to support.

Send a single SMS

POST/api/v1/comms/sms

Access
API key. Scoped keys need sms: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.

Forwards the message to the route seller and returns straight away with a message id. status is the send-time outcome (accepted, sent or failed). The final outcome comes later from the carrier's delivery receipt, when the route returns one: read it with GET /comms/sms/{messageId} or subscribe to the sms.delivered and sms.failed webhooks. A message that fails on its receipt is refunded. Billed per segment at the route price plus the platform fee. A test key simulates the send against test credit.

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

    Destination number (E.164)
  • fromrequired

    string

    Sender ID: a number or an alphanumeric sender (letters, digits, spaces and . + _ -)max 30 chars
  • routeId

    string (uuid)

    A route you purchased. Omit to let Smart Routing pick one
  • strategy

    string

    Smart Routing strategy used when routeId is omittedOne of cheapest, best_quality, balanced
  • messagerequired

    string

    Message body. Long messages are sent as concatenated segmentsmax 1600 chars

Response 200

  • datarequired

    object

  • messageIdrequired

    string · data.messageId

    Use with GET /comms/sms/{messageId}
  • torequired

    string · data.to

  • fromrequired

    string · data.from

  • statusrequired

    string · data.status

    Send-time outcome when we forwarded the message, not a handset delivery receiptOne of sent, delivered, failed, pending, accepted
  • segmentsrequired

    integer · data.segments

    Billed segments (160 GSM-7 / 70 UCS-2 characters each, fewer when concatenated)
  • costrequired

    money · data.cost

    USD as a decimal string with exactly 6 places, e.g. "0.012500".
  • submittedAtrequired

    string (date-time) · data.submittedAt

    ISO-8601 timestamp (UTC)
  • simulated

    boolean · data.simulated

    Present and true for test-key messages: nothing was sent
  • network

    object | null · data.network

    Present when the route prices SMS to this country per destination network. The network is determined from the number's range (ported numbers may be priced at the network the range belongs to)
  • mccMncrequired

    string | null · data.network.mccMnc

    Mobile network code (MCC-MNC) the message was priced as, e.g. "234-10"
  • operatorrequired

    string | null · data.network.operator

    Network name from public number-range data
  • sourcerequired

    string · data.network.source

    range = number-range data; hlr = a live network lookup; none = not determinedOne of range, hlr, none
  • rateBasisrequired

    string · data.network.rateBasis

    network = that network's own rate; all_operators = the route's rate for networks it does not list separately; country = the route's price for other or unknown networksOne of network, all_operators, country

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 delivery status and timeline of a sent SMS

GET/api/v1/comms/sms/{messageId}

Access
API key. Scoped keys need sms:send.
Rate limit
100 requests per second (the default)

Returns the message's current status and its timeline: queued (charged), sent (the route accepted the hand-off), then delivered or failed, each with a timestamp, and an errorCode on failure.

Where delivered comes from. Only from a carrier delivery receipt. Receipts come back on SMPP routes whose supplier returns them, and on HTTP routes whose seller posts them back to us. A route that returns none leaves its messages at sent with awaitingReceipt: true; routeReturnsReceipts tells you whether the route has returned any in the last 30 days, so you know whether to wait. A receipt that reports the message undelivered, expired or rejected marks it failed and refunds it.

Covers messages sent with POST /comms/sms and POST /comms/sms/bulk. An unknown id answers 200 with status not_found rather than 404.

Parameters

  • messageIdrequired

    path · string

Response 200

  • datarequired

    object

  • messageIdrequired

    string · data.messageId

  • statusrequired

    string · data.status

    Current state: sent, accepted (test key), delivered or failed; not_found when the id is not on your account. delivered only ever comes from a carrier receipt
  • to

    string | null · data.to

  • from

    string | null · data.from

  • segments

    integer | null · data.segments

  • errorCode

    string | null · data.errorCode

    Set when status is failed: SELLER_REJECTED (the route refused the hand-off), NO_ENDPOINT, UNDELIVERABLE, EXPIRED or REJECTED (from the carrier receipt)
  • timeline

    object[] · data.timeline

    Every state the message has been in, oldest first
  • statusrequired

    string · data.timeline[].status

    queued = charged and waiting to be handed off; sent = the route accepted it; accepted = simulated on a test key; delivered / failed = finalOne of queued, sent, accepted, delivered, failed
  • atrequired

    string (date-time) · data.timeline[].at

    ISO-8601 timestamp (UTC)
  • sourcerequired

    string · data.timeline[].source

    Where we learned it: our ledger, the hand-off to the route, the carrier's delivery receipt, or a test-key simulationOne of platform, submit, carrier_receipt, simulated
  • errorCode

    string | null · data.timeline[].errorCode

    On failed: SELLER_REJECTED, NO_ENDPOINT, UNDELIVERABLE, EXPIRED or REJECTED
  • carrierStatus

    string | null · data.timeline[].carrierStatus

    The carrier receipt's own status value, e.g. DELIVRD or UNDELIV
  • carrierError

    string | null · data.timeline[].carrierError

    The carrier receipt's own error value, when it sent one
  • awaitingReceipt

    boolean · data.awaitingReceipt

    True while the message is sent and no carrier receipt has arrived. It stays true for good on a route that returns no receipts
  • routeReturnsReceipts

    boolean | null · data.routeReturnsReceipts

    Whether the route that carried this message has returned at least one carrier receipt in the last 30 days. null for test-key messages
  • dlrSupported

    boolean · data.dlrSupported

    True: the platform collects carrier delivery receipts. Whether one arrives for this message depends on the route (routeReturnsReceipts)
  • simulated

    boolean · data.simulated

    Present and true for test-key messages: nothing was sent
  • cost

    money · data.cost

    The ledger amount of the charge (negative) USD as a decimal string with exactly 6 places, e.g. "0.012500".
  • reference

    string | null · data.reference

  • sentAt

    string (date-time) · data.sentAt

    When the message was charged
  • message

    string · data.message

    Explanation, present when status is not_found

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 up to 1,000 SMS in one request

POST/api/v1/comms/sms/bulk

Access
API key. Scoped keys need sms:send.
Rate limit
100 requests per second (the default)
Real traffic
Places a real call or sends a real message, billed to your balance. A test key simulates it against test credit.

Sends each message in order over one route and returns a result per recipient. Each message is billed exactly as POST /comms/sms bills it: a reseller sub-account API key pays the sub-account's retail rate, a test key simulates. Stops at the first insufficient-balance refusal (account or sub-account) and marks the rest skipped. Not idempotent: retrying resends every message.

Request body (application/json)

  • routeIdrequired

    string (uuid)

    The purchased SMS route every message is sent over
  • fromrequired

    string

    Sender ID for every messagemax 30 chars
  • messagesrequired

    object[]

  • torequired

    string · messages[].to

    max 30 chars
  • messagerequired

    string · messages[].message

    max 1600 chars

Response 200

  • datarequired

    object

  • totalrequired

    integer · data.total

  • sentrequired

    integer · data.sent

  • failedrequired

    integer · data.failed

    Failed plus skipped
  • totalCostrequired

    money · data.totalCost

    USD as a decimal string with exactly 6 places, e.g. "0.012500".
  • resultsrequired

    object[] · data.results

  • torequired

    string · data.results[].to

  • statusrequired

    string · data.results[].status

    The send status, or failed / skipped
  • messageId

    string · data.results[].messageId

  • from

    string · data.results[].from

  • segments

    integer · data.results[].segments

  • cost

    money · data.results[].cost

    US dollars as a decimal string with exactly 6 decimal places, e.g. "0.012500". Do money arithmetic with a decimal type, not floating point. USD as a decimal string with exactly 6 places, e.g. "0.012500".
  • submittedAt

    string (date-time) · data.results[].submittedAt

    ISO-8601 timestamp (UTC)
  • error

    string · data.results[].error

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.