Skip to content
Markets open
Quickstart · Numbers and routing

Check routes before you buy

Before you buy, learn whether a route actually carries calls to your destination right now. A route check places one short real call over each route you pick and tells you, in one sentence per route, whether it worked.
Calls
POST /routes/check · GET /routes/check/{id} · GET /routes/check/coverage · POST /routes/check/{id}/purchase · POST /routes/check/sms
Scope
voice:send
Cost
The route's rate for the seconds the check call was connected (usually about two), plus 2% of the route rate, never more than $0.001. A route that does not connect costs nothing, and checking your own listing is free.

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 voice:send to start a check, routes:read to read it and purchases:write to buy. You can check any public marketplace route before you buy it, or a private route you have access to. A test key returns a simulated result at once: no call, no charge.

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.

A check is billed like a call, so it needs a funded balance. Limits: 10 checks a minute and 60 an hour per account.

CostThe route's rate for the seconds the check call was connected (usually about two), plus 2% of the route rate, never more than $0.001. A route that does not connect costs nothing, and checking your own listing is free.

Make the request

Send 1 to 3 route ids in your order: the first is your primary, the rest backups. Add the number you plan to call (or its dial code) so the check uses a line of the same kind, mobile or fixed. The call goes to a public line in the destination that answers by itself and is ended after about two seconds. Set mode to ring_me with your own number to have the routes ring your phone instead.

# 1. Check up to three routes, in your order: the first is your primary, the rest backups.
check_id=$(curl -s https://packetexchange.io/api/v1/routes/check \
  -H "Authorization: Bearer $PACKETEXCHANGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"routeIds\": [\"$ROUTE_1\", \"$ROUTE_2\"], \"number\": \"+923001234567\"}" |
  jq -r .data.checkId)

# 2. Read the verdicts until status is done (usually well under a minute).
curl -s "https://packetexchange.io/api/v1/routes/check/$check_id" \
  -H "Authorization: Bearer $PACKETEXCHANGE_API_KEY" |
  jq '.data | {status, totalCost, routes: [.routes[] | {role, routeName, result, message}]}'

# 3. Buy the routes that passed; they go to the top of your routing order, primary first.
curl -s -X POST "https://packetexchange.io/api/v1/routes/check/$check_id/purchase" \
  -H "Authorization: Bearer $PACKETEXCHANGE_API_KEY" |
  jq '.data.routingOrder'
Save as request.sh, run bash request.shNeeds curl, plus jq for the multi-step samplesPass one to three route ids from the marketplace or from price-a-number, primary first.

Handle the response

POST returns 202 with a checkId straight away. Read the check until status is done; each route carries its verdict and the sentence to show.

202 AcceptedPOST /routes/check
{
  "success": true,
  "data": { "checkId": "<uuid>" }
}
200 OKGET /routes/check/{id}
{
  "success": true,
  "data": {
    "id": "<uuid>",
    "status": "done",
    "mode": "network",
    "simulated": false,
    "number": "+923001234567",
    "totalCost": "<USD, 6 decimals>",
    "routes": [
      {
        "position": 0,
        "role": "primary",
        "routeId": "<route id>",
        "routeName": "<route name>",
        "result": "working",
        "message": "This route is working for your needs.",
        "checkType": "full",
        "destination": "Pakistan",
        "lineType": "mobile",
        "answerMs": 4210,
        "billableSeconds": 2,
        "cost": "<USD, 6 decimals>",
        "paidFrom": "test_credit"
      },
      {
        "position": 1,
        "role": "backup",
        "routeId": "<route id>",
        "routeName": "<route name>",
        "result": "no_capacity",
        "message": "No capacity on this route right now. Choose another route.",
        "checkType": "full",
        "sipCode": 503,
        "billableSeconds": 0,
        "cost": "0.000000"
      }
    ],
    "working": ["<route id>"]
  }
}

Values in angle brackets are placeholders for your own ids, times and amounts.

result
working (answered), no_capacity (refused or no response), not_delivered (rang but never reached the line), false_answer (answered under 1.5 seconds after our INVITE, or under 2.5 seconds with no ringing: faster than a real line can answer, so the route almost certainly answered the call itself; not charged), inconclusive (no verdict, nothing counted against the route) or accepts (the lighter check below).
answerMs
Milliseconds from our INVITE to the answer, when the call was answered. The evidence behind false_answer.
paidFrom
Which wallet paid a charged leg: test_credit, balance or mixed. A check is a test call, so your test credit is used first and your balance only for the rest. GET /routes/check/coverage tells you, per route, whether we have a test line; when we do not, check with mode ring_me and a number of your own.
checkType
full is a call to a test line; light means we have no test line for that destination yet, so we only confirmed the route accepts calls and called no one; ring_me is your own phone; simulated is a test key.
message
The plain sentence to show a person.
working
The route ids that passed, in your order.

When something goes wrong

  • POST /routes/check/{id}/purchase buys the routes that passed and puts them at the top of your routing order, the first as primary. Routes you already bought are reused, and a route that cannot be bought is reported with its reason.
  • Route names in the response never identify the seller.
  • For an SMS route, POST /routes/check/sms with { "routeId", "number" } sends one fixed test message to your own mobile (scope sms:send). Read it with the same GET /routes/check/{id}: accepted, then delivered once a carrier receipt confirms it, or refused with an errorCode. A refused message is not charged.
  • Errors use the standard envelope: { "success": false, "error": { "code", "message", "details" } }; too many checks answer 429 RATE_LIMITED.
400The error envelope
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid input",
    "details": [
      { "path": "to", "message": "<why the field was refused>" }
    ]
  }
}

Go further

Start building

Run it with your own key

Open an account, add credit from $5 and create a key. You pay the carrier's rate plus a fee capped at $0.001.