Developers
API changelog
Changes to the REST API that can affect an integration, newest first. Where a change replaces something, the old behaviour keeps working during a stated transition.
Added
Send a test SMS over an SMS route before you buy
POST /routes/check/sms sends one short fixed message ("PacketExchange test message: this route delivered to your phone. Ref K7QD") over exactly the SMS route you name to your own mobile, which the route must cover, and returns a checkId; GET /routes/check/{id} reports accepted (the route took it, no receipt yet), delivered (a carrier receipt confirmed it) or refused (with errorCode, and the carrier's own stat and err values when a receipt said it failed). It is charged at the route's price for one message plus the normal platform fee, from your test credit first; a refused message is not charged and one whose receipt says it failed is refunded. The text and sender are fixed. Limits: 3 test messages to one number an hour, 5 different numbers and 20 test messages per account a day. Test keys return a simulated result and send nothing. Tests appear in GET /routes/checks with mode sms and never count in SMS volume, revenue or quality figures. Route check results gain the accepted, delivered and refused values and the checkType sms.
- POST/routes/check/sms
- GET/routes/check/{id}
- GET/routes/checks
Added
Automatic failover on every Smart Routing call and SIP trunk
A call placed without a routeId that a route refuses before it is answered (SIP 403, 480, 500, 502, 503, 504 or no response) is now tried straight away on your next route: your routing order for that number, else Smart Routing's next choice, up to three routes. Only the route that connects is billed; a wrong number (404, 484), a busy or declined call and no answer are never retried, and a Voice API call that has started ringing is never moved to another route. The response, the call.completed webhook and GET /comms/calls/{id} name the route that carried the call (routeId) and list the attempts when there was more than one. Calls over your SIP trunk fail over across your purchased routes for the number in the same order when a route answers with a server error or congestion (5xx), and GET /purchases/failover shows your first choice and backups per destination.
- POST/comms/calls
- GET/comms/calls/{id}
- GET/purchases/failover
Added
Measured route quality on every listing
Marketplace listings now carry a measured object: ASR, ACD, NER (the share of calls the network delivered to the phone: answered, busy, unanswered or declined) and median PDD, measured on real buyer calls over the last 7 days, else 30 days, else the most recent period that had enough. It is null until a listing has carried at least 50 calls from at least two buyers; route checks, test calls, the seller's own traffic and calls refused before dialling are not counted, and a call that failed over counts once on each route it tried. Rows also carry routeChecks (the last 7 days of route checks, or null). The seller-stated expectedAsr, expectedAcd and expectedPdd are unchanged. New sorts: measured_asr_desc, measured_asr_asc, measured_acd_desc, measured_acd_asc, ner_desc, ner_asc and quality_desc. The Exchange Score uses measured figures when a listing has them for the last 7 or 30 days. Signed-out callers get the call count as a band and no buyer count.
- GET/routes
- GET/routes/{id}
- GET/routes/price-number
Added
Route checks: check up to three routes before you buy
POST /routes/check places one short real call over each of 1 to 3 routes, in your order (the first is your primary, the rest backups), and returns a checkId; GET /routes/check/{id} gives each route's verdict (working, no capacity, not delivered, false answer, or not confirmed) with a plain sentence to show. A call answered faster than a real line can answer (under 1.5 seconds after our INVITE, or under 2.5 seconds with no ringing) is reported as a false answer, never as working, and is not charged. By default the call goes to a public line in the destination that answers by itself and is ended after about two seconds; where we have no such line yet, a lighter check confirms only that the route accepts calls and places no call to anyone. With mode ring_me the route calls your own phone instead. Each connected call is charged at the route's rate for the connected seconds plus the normal platform fee, from your test credit first and then your balance, and a call that does not connect costs nothing; GET /routes/check/coverage says whether we have a test line for a route, and when we do not, ring_me with a number of your own is the check to use; test keys return a simulated result with no call and no charge. POST /routes/check/{id}/purchase buys the routes that passed and puts them at the top of your routing order, primary first.
- POST/routes/check
- GET/routes/check/{id}
- GET/routes/checks
- GET/routes/check/coverage
- POST/routes/check/{id}/purchase
Changed
SMS priced by destination network
On SMS routes whose rate sheet prices a country per mobile network, each message is now charged the price of the network the number is on, instead of the country's highest network price; a number whose network cannot be determined pays the country price, which is never exceeded. The network is determined from number ranges, so a ported number may be priced at the network its range belongs to. The SMS send response carries a network object (network code, name, and whether the network's own price applied), GET /routes/price-number and GET /lookup/{number} quote the price for the number's network, and a route's rates list each network's price.
- POST/comms/sms
- GET/routes/price-number
- GET/lookup/{number}
- GET/routes/{id}/rates
Added
Asynchronous calls, call actions and call status
POST /comms/calls takes async: true and returns at once (202) with a callId instead of waiting for the call to end; GET /comms/calls/{id} gives the live status, timestamps, duration, cost and the hangup reason in plain words, and the new call.ringing, call.answered and call.gathered webhooks follow the call. An actions list makes the answered call speak text (say, in en, es, fr, de, pt or hi), play an MP3, collect keypad digits (gather), pause or hang up, with no charge for the speech. Without async the endpoint still blocks and returns exactly what it did before.
- POST/comms/calls
- GET/comms/calls/{id}
Added
SMS delivery status and delivery webhooks
GET /comms/sms/{id} now returns the message timeline: queued, sent, then delivered or failed, each with a timestamp, and an error code on failure. Delivered comes only from the carrier's delivery receipt; on a route that returns none the message stays sent, and routeReturnsReceipts says whether the route has returned any in the last 30 days. New webhook events sms.delivered and sms.failed fire on the receipt (sms.failed also fires when the route refuses the message), and a message that fails is refunded. sms.dlr is unchanged: it is the send-time receipt for SMPP traffic.
- GET/comms/sms/{messageId}
Added
Number lookup
GET /lookup/{number} validates and formats an E.164 number and returns its country, line type (mobile, fixed, toll free or premium), the network where the rate decks on the exchange agree, whether it is a blocked or high-risk destination, and the cheapest live voice and SMS price. It is prefix-based: no carrier network query is made, so it cannot see porting. Free, 60 lookups a minute, cached for up to 10 minutes.
- GET/lookup/{number}
Changed
Manage webhook endpoints with an API key
Creating, editing, deleting and rotating the secret of a webhook endpoint now works with an API key that holds the new webhooks:write permission, as well as from the dashboard. A full-access key does not include webhooks:write; it has to be chosen when the key is created, so no existing key gained it.
- POST/account/webhooks
- PATCH/account/webhooks/{id}
- DELETE/account/webhooks/{id}
- POST/account/webhooks/{id}/rotate-secret
Changed
Test keys cannot change the Switch or top up with x402
A test key (wmmn_test_sk_) is now refused with 403 TEST_KEY_NOT_ALLOWED on any request that changes Switch data and on POST /topups/x402. Neither has a test mode, so these requests used to act on the live account. Test keys can still read Switch data.
- POST/topups/x402
Changed
Number search result under data
GET /dids/search now returns its result under data, like every other endpoint. The top-level hits, scannedPages and truncated fields are still sent, so existing clients keep working; they are deprecated.
- GET/dids/search
Fixed
Inbound SMS forwards are signed with your webhook secret
The forward of an inbound text on a phone number was signed with a platform key you could not verify. It is now signed like every other webhook: X-PX-Timestamp and X-PX-Signature: v1=, using the secret of your webhook endpoint at the same URL, or else your newest active endpoint. X-PX-Webhook-Endpoint names which one. With no webhook endpoint the forward is sent unsigned.
Added
Verify API and voice passcodes
POST /verify/start sends a one-time code by SMS or by a voice call that reads the digits aloud, and POST /verify/check answers approved, denied, expired or max_attempts (5 attempts, single use, 10-minute default expiry). Only a hash of the code is stored. POST /comms/voice-otp places the voice call on its own. Billed as the SMS or call, with no per-verification fee. Needs the new verify:write scope (voice-otp uses voice:send); test keys simulate and return testCode.
- POST/verify/start
- POST/verify/check
- GET/verify/{id}
- POST/comms/voice-otp
Added
Price a number across the marketplace
Give a phone number and get every route that serves it, at the rate that route would actually charge for that number (its longest matching rate-sheet row, or its flat price), cheapest first. Works signed out; seller identities are never included.
- GET/routes/price-number
Added
Routing order for your purchased routes
When several of your voice purchases cover a number equally well, your routing order now decides which carries the call, then the cheaper rate, then the older purchase. Set the whole order or one position, and ask which of your routes would carry a given number, ranked exactly as the live call path ranks them.
- GET/purchases/routing-order
- PUT/purchases/routing-order
- PATCH/purchases/{id}/routing-priority
- DELETE/purchases/{id}/routing-priority
- GET/purchases/route-for
Added
Scheduled rate changes with advance notice
Rate increases from a rate sheet, and flat price changes, can now be scheduled for a future date after a notice period instead of applying at once. Sellers can list and cancel scheduled changes, schedule a flat price change, and set when a sheet's increases take effect with increaseEffectiveAt. Buyers can list the scheduled changes on a route they bought and accept one in advance by passing its changeId to accept-rate, so their purchase keeps running on the date instead of pausing for re-consent.
- GET/purchases/{id}/upcoming-rate-changes
- POST/purchases/{id}/accept-rate
- GET/routes/{id}/rate-changes
- POST/routes/{id}/rate-changes/flat
- POST/routes/{id}/rate-changes/{changeId}/cancel
Added
Rate-change notice for Switch customers
Set a notice period per Switch customer: a sell-rate increase is then queued and takes effect that many days out, while decreases and new destinations still apply at once. Review the queued changes as JSON or CSV, and email the customer a notice under your own brand.
- GET/switch/customers/{id}/sell-rates/notice
- PUT/switch/customers/{id}/sell-rates/notice
- GET/switch/customers/{id}/sell-rates/notice/changes
- GET/switch/customers/{id}/sell-rates/notice/changes.csv
- POST/switch/customers/{id}/sell-rates/notice/send
Added
Route liveness tests
Test 2 to 20 voice routes in one go: each gets a real test call to a handset in its destination country, and you see which rang and what caller ID was displayed. Preview the cost first; a route is charged only if it rang, and cancelling stops every route not yet called.
- POST/cli-tests/batches/preview
- POST/cli-tests/batches
- GET/cli-tests/batches
- GET/cli-tests/batches/{id}
- POST/cli-tests/batches/{id}/cancel
Added
Do Not Call on the Switch
Turn "Honor Do Not Call" on per Switch customer or per trunk, and calls from them are checked against your Do Not Call list and the platform list. Also new: check a single number, scrub an uploaded file of up to 500,000 numbers, import and export your list, and see the calls that matched. The list is the same one managed under /dnc.
- GET/switch/dnc/summary
- GET/switch/dnc/entries
- GET/switch/dnc/hits
- GET/switch/dnc/check
- GET/switch/dnc/customers
- GET/switch/dnc/honor
- PATCH/switch/dnc/honor
- POST/switch/dnc/scrub
- GET/switch/dnc/scrub/{id}
- POST/switch/dnc/import
- GET/switch/dnc/export
Added
Listing health and bulk endpoint changes
See how many of your listed routes are live on the marketplace and why the rest are hidden, then fix them in one call: set one SMS delivery method or one SIP endpoint on a list of routes or on every route matching a filter (up to 5,000). A dry run shows what would change, and the endpoint is checked once before anything is written. The My routes list gains search, filters, sorting and offset paging with a total.
- GET/routes/my/listing-health
- POST/routes/my/bulk-sms-delivery
- POST/routes/my/bulk-sip-endpoint
- GET/routes/my/list
Added
Labels on saved SIP endpoints
Give a saved SIP endpoint a label (up to 60 characters) when you save it, or rename it later, so you can tell your switches apart in the endpoint pickers. An empty label clears it.
- POST/routes/my-endpoints
- PATCH/routes/my-endpoints/{id}
Changed
Route resolve returns price as a decimal string
The price on each resolved route is now a USD decimal string with 6 places ("0.012500"), like every other money field in the API. It was a JSON number. Parse it with a decimal type rather than floating point.
- GET/routes/resolve
Added
keyPrefix on API key creation
Creating an API key now returns keyPrefix, the same field name the list and update responses use. The original prefix field is still returned for existing clients.
- POST/account/api-keys
Changed
X-Request-Id is a UUID
Every response carries an X-Request-Id that is now a UUID, unique across all API nodes. Quote it when contacting support.
Added
Timestamped webhook signatures
Deliveries now carry X-PX-Timestamp (Unix seconds) and X-PX-Signature: v1=<HMAC-SHA256 of "timestamp.body">, so a receiver can reject replayed deliveries. The legacy X-Webhook-Signature (HMAC of the body alone) is still sent during the transition, so existing receivers keep working.
Added
Webhook delivery history, resend and per-key API usage
List deliveries across all your webhook endpoints (filter by endpoint, status or event), read one delivery with its payload, and resend it. API usage can now be filtered to one API key with keyId.
- GET/account/webhooks/deliveries
- GET/account/webhooks/deliveries/{deliveryId}
- POST/account/webhooks/deliveries/{deliveryId}/resend
- GET/account/api-usage
Added
Complete OpenAPI reference
The API reference now documents request and response schemas for the customer-facing API, rate limits generated from the live route configuration, and the full error-code list, including INVALID_INPUT.