Get an API key
Open an account, then create a key under Dashboard, API keys. Live keys (wmmn_live_sk_) send real traffic and charge your balance. Test keys (wmmn_test_sk_) run the same request through routing and pricing without delivering it, and draw on test credit, which comes with an invite code.
A key restricted to scopes needs sms:send. A test key runs the request through routing and pricing and returns simulated: true without sending anything.
Every sample reads the key from the environment. Keep it out of source control.
export PACKETEXCHANGE_API_KEY=wmmn_live_sk_...Fund the balance
PacketExchange is prepaid. Top up from $5 by card or crypto, or from $100 by wire, under Dashboard, Billing. An agent can top itself up with x402. There is no contract and no monthly fee.
The message is charged to your prepaid balance at the route rate per segment, plus the platform fee. Long messages split into segments: 160 GSM-7 characters (153 per part when split) or 70 Unicode (67 per part).
CostThe route rate per segment, plus 2% of the route rate, never more than $0.001 per message. The response states the exact cost.
Make the request
Send to an E.164 number from a sender ID: a number, or up to 30 letters, digits and spaces. The X-Idempotency-Key header makes a retry safe: the same key never sends or bills the message twice.
curl https://packetexchange.io/api/v1/comms/sms \
-H "Authorization: Bearer $PACKETEXCHANGE_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: $(uuidgen)" \
-d '{
"to": "+447700900123",
"from": "Acme",
"message": "Your order 1042 has shipped."
}'Handle the response
A 200 means the message was handed to the route. The status here is the send-time outcome; delivery comes later, from the carrier's receipt, on routes that return one.
{
"success": true,
"data": {
"messageId": "<message id>",
"to": "+447700900123",
"from": "Acme",
"status": "accepted",
"segments": 1,
"cost": "<USD, 6 decimals>",
"submittedAt": "<ISO-8601 UTC>"
}
}Values in angle brackets are placeholders for your own ids, times and amounts.
- messageId
- Look the message up later with GET /comms/sms/{messageId}.
- status
- accepted, sent or failed at send time. GET /comms/sms/{messageId} later shows delivered or failed when the route returns a carrier receipt (routeReturnsReceipts says whether it does), and the sms.delivered and sms.failed webhooks fire on it. A message that fails on its receipt is refunded.
- segments
- How many segments were billed.
- cost
- What this message cost you, fee included, as a 6-decimal USD string. Do arithmetic with a decimal type.
- simulated
- Present and true on a test key: nothing was sent.
When something goes wrong
- Anything other than 2xx carries { "success": false, "error": { "code", "message", "details" } }. VALIDATION_ERROR lists each bad field in details.
- A 429 carries Retry-After in seconds. Wait that long, then retry with the same idempotency key.
- Quote the X-Request-Id response header if you contact support.
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input",
"details": [
{ "path": "to", "message": "<why the field was refused>" }
]
}
}Go further
- Follow deliveryRead the delivery status with GET /comms/sms/{messageId}, or subscribe to sms.delivered and sms.failed.
- Get sends as webhooksSubscribe to sms.sent and verify each signed delivery.
- Choose the route yourselfPrice the destination first, then pin routeId or pass a strategy.
- SMS on PacketExchangeSender IDs, coverage and how routes are listed.