Skip to content

API reference

Route checks

Check up to three routes with a short real call before you buy them, then buy the ones that passed with the routing order set.

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

Check up to three routes before buying

POST/api/v1/routes/check

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

Places one short real call over each route (1 to 3, in your order: the first is your primary, the rest backups) and reports whether it works. Returns 202 with a checkId at once; poll GET /routes/check/{checkId}.

- network (default): the call goes to a public line that answers by itself (a speaking clock or information line) in the destination, chosen from the ones the route's rates cover and, where we have one, of the same line type as number (mobile or fixed). The call is answered, held about two seconds and ended. Where no such line exists yet for a destination, a lighter check runs instead: it confirms only that the route accepts calls, places no call to anyone, and says so (accepts). - ring_me: the route calls number, which must be your own phone in a country the route serves. Ringing counts as working. Satellite and premium-rate ranges are refused; one number can be rung by checks 3 times an hour, and an account can ring 3 different numbers a day (429).

number (network mode) narrows the check to a destination: a dial code (92) or a full number (+923001234567); for an A-Z rate sheet pass it, or the route's own country is used.

Billing: each connected leg is charged at the route's rate (your own effective rate for that number) for the billable seconds, plus the normal platform fee, from your test credit first and then your balance (paidFrom says which). Unanswered legs and suspected false answers cost nothing; a check of your own listing is free. A funded balance is required (400 otherwise). Limits: 10 checks a minute and 60 an hour per account (429); when the limiter cannot be reached checks pause (503) rather than run unlimited. Send X-Idempotency-Key to make a retry safe: the retry returns the first check instead of placing a second. Blended routes cannot be checked yet (400). A test key returns a simulated result immediately: no call, no charge.

You may check routes you have not bought. Private routes need an access grant.

Request body (application/json)

  • routeIdsrequired

    string (uuid)[]

    1 to 3 route ids, in your order: the first is your primary, the rest backups
  • number

    string

    network mode: a dial code or full number to check for (narrows an A-Z rate sheet to that destination and line type). ring_me mode: your own number, requiredmax 32 chars
  • mode

    string

    network = our test lines (default); ring_me = the route calls your own numberdefault "network"One of network, ring_me

Response 202

  • datarequired

    object

  • checkIdrequired

    string (uuid) · data.checkId

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.
  • AlsoRATE_LIMITED, SERVICE_UNAVAILABLE

Send a test SMS over an SMS route

POST/api/v1/routes/check/sms

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

Sends ONE short fixed message over exactly this SMS listing (never Smart Routing) to your own mobile, by the same transport a real message uses, and reports what happened. Returns 202 with a checkId at once; poll GET /routes/check/{checkId} (the test is its only route). The text is fixed and so is the sender: "PacketExchange test message: this route delivered to your phone. Ref K7QD", from a platform sender chosen by the route's sender rules (numeric on a route that carries numeric senders only).

Results: accepted (the route took it; no carrier receipt yet), delivered (a carrier receipt confirmed it) or refused (the route refused it, or the receipt said it failed; errorCode says which). Receipts usually arrive within a minute; on a route that returns none the test stays accepted, and routeReturnsReceipts says whether the route has returned any lately. A later receipt updates the test, so it also appears in GET /routes/checks.

number must be a mobile in international format that the route covers (by its rate sheet or its prefixes, longest prefix first); a number it does not cover is refused (400), as are premium-rate, satellite, special-service and short-code ranges, embargoed destinations and numbers on your Do-Not-Contact list.

Billing: the route's normal price for one message for that number (your own effective rate), plus the normal platform fee, from your test credit first and then your balance (paidFrom). A message the route refuses is not charged, and one whose receipt says it failed is refunded. Testing your own listing is free. Limits: 3 test messages to one number an hour (across all accounts), 5 different numbers and 20 test messages per account a day (429); when the limiter cannot be reached tests pause (503). Send X-Idempotency-Key to make a retry safe. A test key returns a simulated result at once: nothing is sent and nothing is charged.

Test messages never count in SMS volume, revenue or quality figures. A buyer's delivered test counts as working and a refused one as a failure on the listing's check health, which the seller sees without learning who ran it.

Request body (application/json)

  • routeIdrequired

    string (uuid)

    The SMS listing to test (a marketplace route id)
  • numberrequired

    string

    Your own mobile number in international format (E.164), e.g. +35056012345. The route must cover itmax 32 chars

Response 202

  • datarequired

    object

  • checkIdrequired

    string (uuid) · data.checkId

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.
  • AlsoRATE_LIMITED, SERVICE_UNAVAILABLE

Is there a test line for these routes?

GET/api/v1/routes/check/coverage

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

For each route (1 to 3), whether we have a registered public test line for its destination (or for number's destination on an A-Z rate sheet). When there is none, check the route with mode: ring_me and your own number. Says yes or no only.

Parameters

  • routeIdsrequired

    query · object

    Comma-separated route ids (1 to 3)
  • number

    query · string

    Optional dial code or number, to ask about one destination of an A-Z rate sheet

Response 200

  • datarequired

    object[]

  • routeIdrequired

    string (uuid) · data[].routeId

  • testLinerequired

    boolean · data[].testLine

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.

Get a route check's results

GET/api/v1/routes/check/{id}

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

Per-route verdicts, in your order. message is the sentence to show a person. Routes are named without seller details.

Parameters

  • idrequired

    path · string (uuid)

Response 200

  • datarequired

    object

  • idrequired

    string (uuid) · data.id

  • statusrequired

    string · data.status

    Poll GET /routes/check/{id} until done (usually under 40 seconds)One of running, done
  • moderequired

    string · data.mode

    sms = a test message (POST /routes/check/sms)One of network, ring_me, sms
  • simulatedrequired

    boolean · data.simulated

    True for a test key: no call was placed and nothing was charged
  • numberrequired

    string | null · data.number

    The number or dial code you checked for (+digits)
  • createdAtrequired

    string (date-time) · data.createdAt

    ISO-8601 timestamp (UTC)
  • completedAtrequired

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

    ISO-8601 timestamp (UTC)
  • totalCostrequired

    money · data.totalCost

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

    object[] · data.routes

  • positionrequired

    integer · data.routes[].position

    0 = your first choice (primary), then backups
  • rolerequired

    string · data.routes[].role

    One of primary, backup
  • routeIdrequired

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

  • routeNamerequired

    string · data.routes[].routeName

    Cleaned of seller names and contact details
  • countryrequired

    string · data.routes[].country

  • ownRouterequired

    boolean · data.routes[].ownRoute

    A listing you sell yourself (checked free)
  • resultrequired

    string · data.routes[].result

    pending/checking = in progress. working = the call was answered: the route delivers. no_capacity = the route refused the call or did not respond. not_delivered = the route signalled ringing but the call never reached the number. false_answer = the route answered faster than a real line can be reached (under 1.5 s from our INVITE, or under 2.5 s with no ringing): it almost certainly answered the call itself. Not working, and not charged. inconclusive = no verdict (our test lines were busy, the number was reported invalid, or the check was interrupted); nothing counted against the route. accepts = the lighter check for a destination without a full check yet: the route accepts calls. Test SMS only: accepted = the route took the message and no carrier receipt has arrived yet (on a route that returns none it stays accepted); delivered = a carrier receipt confirmed delivery to the phone; refused = the route refused the message, or the carrier receipt said it failed (see errorCode). A refused message is not charged.One of pending, checking, working, no_capacity, not_delivered, false_answer, inconclusive, accepts, accepted, delivered, refused
  • messagerequired

    string · data.routes[].message

    The plain-English verdict to show, e.g. "This route is working for your needs."
  • checkTyperequired

    string | null · data.routes[].checkType

    full = a call to one of our test lines; light = acceptance only; ring_me = your own number; sms = a test message to your own mobile; simulated = test keyOne of full, light, ring_me, sms, simulated
  • destinationrequired

    string | null · data.routes[].destination

    The country the check call went to
  • lineTyperequired

    string | null · data.routes[].lineType

    Line type of the test line used (full checks)One of fixed, mobile
  • rangrequired

    boolean | null · data.routes[].rang

  • answeredrequired

    boolean | null · data.routes[].answered

  • pddMsrequired

    integer | null · data.routes[].pddMs

    Post-dial delay: dial to first ring (or answer)
  • sipCoderequired

    integer | null · data.routes[].sipCode

    Final SIP response from the route, when one came back
  • billableSecondsrequired

    integer · data.routes[].billableSeconds

  • costrequired

    money · data.routes[].cost

    What this leg cost you (route rate for the connected seconds, plus the platform fee) USD as a decimal string with exactly 6 places, e.g. "0.012500".
  • paidFromrequired

    string | null · data.routes[].paidFrom

    Which wallet paid: test credit is used first, then your balance. Null when nothing was chargedOne of test_credit, balance, mixed
  • answerMsrequired

    integer | null · data.routes[].answerMs

    Milliseconds from our INVITE to the answer, when it answered (the false-answer test)
  • completedAtrequired

    string (date-time) | null · data.routes[].completedAt

    ISO-8601 timestamp (UTC)
  • messageRefrequired

    string | null · data.routes[].messageRef

    Test SMS: the 4-character reference printed at the end of the message, so you can find it on your phone
  • senderrequired

    string | null · data.routes[].sender

    Test SMS: the sender the platform used (set from the route's sender rules; you cannot choose it)
  • errorCoderequired

    string | null · data.routes[].errorCode

    Test SMS refused: SELLER_REJECTED = the route refused the message; NO_ENDPOINT = the route has no delivery endpoint; UNDELIVERABLE, EXPIRED, REJECTED = the carrier receipt said it failed. The same codes as GET /comms/sms/{id}One of SELLER_REJECTED, NO_ENDPOINT, UNDELIVERABLE, EXPIRED, REJECTED
  • carrierStatusrequired

    string | null · data.routes[].carrierStatus

    Test SMS: the carrier receipt's own stat value (DELIVRD, UNDELIV, ...), when a receipt arrived
  • carrierErrorrequired

    string | null · data.routes[].carrierError

    Test SMS: the carrier receipt's own err value, when it carried one
  • acceptedAtrequired

    string (date-time) | null · data.routes[].acceptedAt

    Test SMS: when the route accepted the message
  • receiptAtrequired

    string (date-time) | null · data.routes[].receiptAt

    Test SMS: when the carrier receipt arrived
  • routeReturnsReceiptsrequired

    boolean | null · data.routes[].routeReturnsReceipts

    Test SMS still accepted: whether this route has returned any carrier receipt in the last 30 days. false = a confirmation may never come; check your phone
  • workingrequired

    string (uuid)[] · data.working

    Route ids that passed (working or accepts; delivered for a test SMS), in your order

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.

List your recent route checks

GET/api/v1/routes/checks

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

Parameters

  • limit

    query · integer

    Default 20

Response 200

  • datarequired

    object[]

    Same fields as RouteCheck, shown earlier on this page.

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.

Buy the routes that passed a check and set the routing order

POST/api/v1/routes/check/{id}/purchase

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

Buys every route in the check that came back working, in your order, and puts them at the top of your routing order: the first is the primary, the rest its backups (your other ranked routes keep their relative order below them). Routes you already bought are reused. A route that cannot be bought (for example email not verified, a private route, or a US route that needs compliance review) is reported in purchases[].error and does not stop the others. Needs a live key.

Parameters

  • idrequired

    path · string (uuid)

Response 200

  • datarequired

    object

  • purchasesrequired

    object[] · data.purchases

  • routeIdrequired

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

  • routeNamerequired

    string · data.purchases[].routeName

  • purchaseIdrequired

    string (uuid) | null · data.purchases[].purchaseId

  • statusrequired

    string | null · data.purchases[].status

    The purchase status (active, or pending_review for a route that needs review)
  • alreadyOwnedrequired

    boolean · data.purchases[].alreadyOwned

  • errorrequired

    string | null · data.purchases[].error

  • routingOrderrequired

    object[] · data.routingOrder

  • positionrequired

    integer · data.routingOrder[].position

    1 = primary
  • rolerequired

    string · data.routingOrder[].role

    One of primary, backup
  • purchaseIdrequired

    string (uuid) · data.routingOrder[].purchaseId

  • routeIdrequired

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

  • routeNamerequired

    string · data.routingOrder[].routeName

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

Check health of a route you sell

GET/api/v1/routes/{id}/check-health

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

For your own listing: the last 24 hours of full route checks (working vs failed), the recent checks, and any suspension made by checks with its reason and evidence. A listing whose checks fail 10 or more times in 24 hours with none working is confirmed once more and then suspended from the marketplace; run POST /routes/check with its id (free for your own listing) after fixing it, and a working result puts it back. A call answered under 1.5 s after our INVITE (or under 2.5 s with no ringing) is a false answer and counts as a failure; answerMs is the evidence. Your own checks, checks to a buyer's own number (ring_me) and lighter checks never count. On an SMS listing the window counts buyers' test messages instead: delivered is working, refused is a failure, a message still awaiting a receipt does not count, and an SMS listing is never suspended automatically.

Parameters

  • idrequired

    path · string (uuid)

Response 200

  • datarequired

    object

  • windowrequired

    object · data.window

  • hoursrequired

    integer · data.window.hours

  • workingrequired

    integer · data.window.working

  • failedrequired

    integer · data.window.failed

  • totalrequired

    integer · data.window.total

  • suspendAfterrequired

    integer · data.window.suspendAfter

    Failures (with none working) that trigger the confirmation check
  • suspensionrequired

    object | null · data.suspension

  • statusrequired

    string · data.suspension.status

    notice = an A-Z rate sheet warned for one country; nothing was suspendedOne of suspended, notice
  • sincerequired

    string (date-time) · data.suspension.since

    ISO-8601 timestamp (UTC)
  • reasonrequired

    string · data.suspension.reason

  • destinationrequired

    string | null · data.suspension.destination

  • evidencerequired

    object · data.suspension.evidence

    Counts, SIP codes, causes and times of the failed checks
  • lastReinstatedAtrequired

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

    ISO-8601 timestamp (UTC)
  • recentrequired

    object[] · data.recent

  • atrequired

    string (date-time) | null · data.recent[].at

    ISO-8601 timestamp (UTC)
  • resultrequired

    string · data.recent[].result

    pending/checking = in progress. working = the call was answered: the route delivers. no_capacity = the route refused the call or did not respond. not_delivered = the route signalled ringing but the call never reached the number. false_answer = the route answered faster than a real line can be reached (under 1.5 s from our INVITE, or under 2.5 s with no ringing): it almost certainly answered the call itself. Not working, and not charged. inconclusive = no verdict (our test lines were busy, the number was reported invalid, or the check was interrupted); nothing counted against the route. accepts = the lighter check for a destination without a full check yet: the route accepts calls. Test SMS only: accepted = the route took the message and no carrier receipt has arrived yet (on a route that returns none it stays accepted); delivered = a carrier receipt confirmed delivery to the phone; refused = the route refused the message, or the carrier receipt said it failed (see errorCode). A refused message is not charged.One of pending, checking, working, no_capacity, not_delivered, false_answer, inconclusive, accepts, accepted, delivered, refused
  • checkTyperequired

    string · data.recent[].checkType

    sms = a test message on an SMS listingOne of full, light, sms
  • errorCoderequired

    string | null · data.recent[].errorCode

    A test message's refusal code; null on calls
  • sipCoderequired

    integer | null · data.recent[].sipCode

  • causerequired

    string | null · data.recent[].cause

  • destinationrequired

    string | null · data.recent[].destination

  • byYourequired

    boolean · data.recent[].byYou

  • runByrequired

    string · data.recent[].runBy

    Who ran it: you, a buyer (never named), or the platform (confirmation / new test line)One of you, buyer, platform
  • answerMsrequired

    integer | null · data.recent[].answerMs

    Milliseconds from our INVITE to the answer
  • rangrequired

    boolean | null · data.recent[].rang

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.