Skip to content
Markets open
Quickstart · Numbers and routing

Buy a phone number

Search by a digit pattern, pick a number group and a SKU, and buy it from code. Calls and texts to the number reach your app, or ring wherever you point it.
Calls
GET /dids/search · POST /dids/buy · PATCH /dids/{id}/routing
Scope
numbers:read, numbers:write
Cost
Each SKU lists its setup price and monthly price. Buying charges the setup price plus the first month; after that the number renews monthly from your balance.

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.

Searching needs numbers:read and buying needs numbers:write on a restricted key. Where identity verification applies, it must be complete before you buy.

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.

Fund at least the setup price plus one month. Keep the balance above the monthly price: a renewal that fails suspends the number for a grace period before it is released.

CostEach SKU lists its setup price and monthly price. Buying charges the setup price plus the first month; after that the number renews monthly from your balance.

Make the request

Run the sample with no arguments to search. Run it again with a groupId and a skuId from the results to buy. The idempotency key makes sure a retry orders one number, not two.

# 1. Search the catalogue. Results are top-level "hits", each with a groupId and SKUs.
curl -s "https://packetexchange.io/api/v1/dids/search?pattern=1415&limit=5" \
  -H "Authorization: Bearer $PACKETEXCHANGE_API_KEY" |
  jq '.hits[] | {groupId, dialingPrefix, country, city, skus}'

# 2. Buy one. This charges the setup price plus the first month to your balance.
curl https://packetexchange.io/api/v1/dids/buy \
  -H "Authorization: Bearer $PACKETEXCHANGE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Idempotency-Key: $(uuidgen)" \
  -d "{\"groupId\": \"$GROUP_ID\", \"skuId\": \"$SKU_ID\"}"
Save as request.sh, run bash request.shNeeds curl, plus jq for the multi-step samplesSearch: no arguments. Buy: pass <groupId> <skuId>.

Handle the response

Search results are top-level hits, not wrapped in data. A new number starts pending and gets its digits when provisioning completes.

200 OKGET /dids/search
{
  "success": true,
  "hits": [
    {
      "groupId": "<group id>",
      "country": "<country>",
      "city": "<city or null>",
      "typeName": "<Local, National, Mobile, Toll-free...>",
      "dialingPrefix": "<country and area prefix>",
      "skus": [
        {
          "skuId": "<sku id>",
          "channels": <integer>,
          "setupPrice": <USD>,
          "monthlyPrice": <USD>
        }
      ]
    }
  ],
  "scannedPages": <integer>,
  "truncated": false
}
201 CreatedPOST /dids/buy
{
  "success": true,
  "data": {
    "id": "<uuid>",
    "number": null,
    "status": "pending",
    "pointMode": "unrouted",
    "setupPrice": "<USD, 6 decimals>",
    "monthlyPrice": "<USD, 6 decimals>",
    "autoRenew": <boolean>,
    "nextRenewalAt": "<ISO-8601 UTC or null>",
    "graceDays": <integer>
  }
}

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

status
pending, active, suspended (a renewal failed), released or failed.
number
null until provisioning completes, then the E.164 number.
pointMode, pointsTo
Where calls go: unrouted, sip (host[:port]) or forward (an E.164 number).
data.disabled
If the number store is unavailable, search answers 200 with disabled: true and a message.

When something goes wrong

  • Route the number with PATCH /dids/{id}/routing and { "mode": "sip", "target": "sip.example.com:5060" }, with an optional backup.
  • Inbound calls and texts arrive as number.call.received and number.sms.received webhooks.
  • Porting existing numbers in is not offered.
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.