Skip to content
Markets open

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)

FieldTypeDescription
routeIdsrequiredstring (uuid)[]1 to 3 route ids, in your order: the first is your primary, the rest backups
numberstringnetwork 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
modestringnetwork = our test lines (default); ring_me = the route calls your own numberdefault "network"One of network, ring_me

Response 202

FieldTypeDescription
datarequiredobject-
checkIdrequireddata.checkIdstring (uuid)-

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)

FieldTypeDescription
routeIdrequiredstring (uuid)The SMS listing to test (a marketplace route id)
numberrequiredstringYour own mobile number in international format (E.164), e.g. +35056012345. The route must cover itmax 32 chars

Response 202

FieldTypeDescription
datarequiredobject-
checkIdrequireddata.checkIdstring (uuid)-

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

NameInTypeDescription
routeIdsrequiredqueryobjectComma-separated route ids (1 to 3)
numberquerystringOptional dial code or number, to ask about one destination of an A-Z rate sheet

Response 200

FieldTypeDescription
datarequiredobject[]-
routeIdrequireddata[].routeIdstring (uuid)-
testLinerequireddata[].testLineboolean-

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

NameInTypeDescription
idrequiredpathstring (uuid)-

Response 200

FieldTypeDescription
datarequiredobject-
idrequireddata.idstring (uuid)-
statusrequireddata.statusstringPoll GET /routes/check/{id} until done (usually under 40 seconds)One of running, done
moderequireddata.modestringsms = a test message (POST /routes/check/sms)One of network, ring_me, sms
simulatedrequireddata.simulatedbooleanTrue for a test key: no call was placed and nothing was charged
numberrequireddata.numberstring | nullThe number or dial code you checked for (+digits)
createdAtrequireddata.createdAtstring (date-time)ISO-8601 timestamp (UTC)
completedAtrequireddata.completedAtstring (date-time) | nullISO-8601 timestamp (UTC)
totalCostrequireddata.totalCostmoneyUSD as a decimal string with exactly 6 places, e.g. "0.012500".
routesrequireddata.routesobject[]-
positionrequireddata.routes[].positioninteger0 = your first choice (primary), then backups
rolerequireddata.routes[].rolestring-One of primary, backup
routeIdrequireddata.routes[].routeIdstring (uuid)-
routeNamerequireddata.routes[].routeNamestringCleaned of seller names and contact details
countryrequireddata.routes[].countrystring-
ownRouterequireddata.routes[].ownRoutebooleanA listing you sell yourself (checked free)
resultrequireddata.routes[].resultstringpending/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
messagerequireddata.routes[].messagestringThe plain-English verdict to show, e.g. "This route is working for your needs."
checkTyperequireddata.routes[].checkTypestring | nullfull = 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
destinationrequireddata.routes[].destinationstring | nullThe country the check call went to
lineTyperequireddata.routes[].lineTypestring | nullLine type of the test line used (full checks)One of fixed, mobile
rangrequireddata.routes[].rangboolean | null-
answeredrequireddata.routes[].answeredboolean | null-
pddMsrequireddata.routes[].pddMsinteger | nullPost-dial delay: dial to first ring (or answer)
sipCoderequireddata.routes[].sipCodeinteger | nullFinal SIP response from the route, when one came back
billableSecondsrequireddata.routes[].billableSecondsinteger-
costrequireddata.routes[].costmoneyWhat 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".
paidFromrequireddata.routes[].paidFromstring | nullWhich wallet paid: test credit is used first, then your balance. Null when nothing was chargedOne of test_credit, balance, mixed
answerMsrequireddata.routes[].answerMsinteger | nullMilliseconds from our INVITE to the answer, when it answered (the false-answer test)
completedAtrequireddata.routes[].completedAtstring (date-time) | nullISO-8601 timestamp (UTC)
messageRefrequireddata.routes[].messageRefstring | nullTest SMS: the 4-character reference printed at the end of the message, so you can find it on your phone
senderrequireddata.routes[].senderstring | nullTest SMS: the sender the platform used (set from the route's sender rules; you cannot choose it)
errorCoderequireddata.routes[].errorCodestring | nullTest 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
carrierStatusrequireddata.routes[].carrierStatusstring | nullTest SMS: the carrier receipt's own stat value (DELIVRD, UNDELIV, ...), when a receipt arrived
carrierErrorrequireddata.routes[].carrierErrorstring | nullTest SMS: the carrier receipt's own err value, when it carried one
acceptedAtrequireddata.routes[].acceptedAtstring (date-time) | nullTest SMS: when the route accepted the message
receiptAtrequireddata.routes[].receiptAtstring (date-time) | nullTest SMS: when the carrier receipt arrived
routeReturnsReceiptsrequireddata.routes[].routeReturnsReceiptsboolean | nullTest 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
workingrequireddata.workingstring (uuid)[]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

NameInTypeDescription
limitqueryinteger-Default 20

Response 200

FieldTypeDescription
datarequiredobject[]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

NameInTypeDescription
idrequiredpathstring (uuid)-

Response 200

FieldTypeDescription
datarequiredobject-
purchasesrequireddata.purchasesobject[]-
routeIdrequireddata.purchases[].routeIdstring (uuid)-
routeNamerequireddata.purchases[].routeNamestring-
purchaseIdrequireddata.purchases[].purchaseIdstring (uuid) | null-
statusrequireddata.purchases[].statusstring | nullThe purchase status (active, or pending_review for a route that needs review)
alreadyOwnedrequireddata.purchases[].alreadyOwnedboolean-
errorrequireddata.purchases[].errorstring | null-
routingOrderrequireddata.routingOrderobject[]-
positionrequireddata.routingOrder[].positioninteger1 = primary
rolerequireddata.routingOrder[].rolestring-One of primary, backup
purchaseIdrequireddata.routingOrder[].purchaseIdstring (uuid)-
routeIdrequireddata.routingOrder[].routeIdstring (uuid)-
routeNamerequireddata.routingOrder[].routeNamestring-

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

NameInTypeDescription
idrequiredpathstring (uuid)-

Response 200

FieldTypeDescription
datarequiredobject-
windowrequireddata.windowobject-
hoursrequireddata.window.hoursinteger-
workingrequireddata.window.workinginteger-
failedrequireddata.window.failedinteger-
totalrequireddata.window.totalinteger-
suspendAfterrequireddata.window.suspendAfterintegerFailures (with none working) that trigger the confirmation check
suspensionrequireddata.suspensionobject | null-
statusrequireddata.suspension.statusstringnotice = an A-Z rate sheet warned for one country; nothing was suspendedOne of suspended, notice
sincerequireddata.suspension.sincestring (date-time)ISO-8601 timestamp (UTC)
reasonrequireddata.suspension.reasonstring-
destinationrequireddata.suspension.destinationstring | null-
evidencerequireddata.suspension.evidenceobjectCounts, SIP codes, causes and times of the failed checks
lastReinstatedAtrequireddata.lastReinstatedAtstring (date-time) | nullISO-8601 timestamp (UTC)
recentrequireddata.recentobject[]-
atrequireddata.recent[].atstring (date-time) | nullISO-8601 timestamp (UTC)
resultrequireddata.recent[].resultstringpending/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
checkTyperequireddata.recent[].checkTypestringsms = a test message on an SMS listingOne of full, light, sms
errorCoderequireddata.recent[].errorCodestring | nullA test message's refusal code; null on calls
sipCoderequireddata.recent[].sipCodeinteger | null-
causerequireddata.recent[].causestring | null-
destinationrequireddata.recent[].destinationstring | null-
byYourequireddata.recent[].byYouboolean-
runByrequireddata.recent[].runBystringWho ran it: you, a buyer (never named), or the platform (confirmation / new test line)One of you, buyer, platform
answerMsrequireddata.recent[].answerMsinteger | nullMilliseconds from our INVITE to the answer
rangrequireddata.recent[].rangboolean | null-

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.