The catalogue tree
Walk the catalogue one scoped level per call — platforms, then countries, then providers, then options — without an API key, and feed the ids straight into an order.
The catalogue is a tree. Each call returns only the level under the choice you made, so you never download the whole market to find one price.
/catalog/platforms every platform in stock
/catalog/platforms/{platform}/countries where that platform is sold
/catalog/platforms/{platform}/countries/{country}/providers who sells it there
/catalog/platforms/{platform}/countries/{country}/providers/{provider}/options what each sells it fromEvery id along the way is exact and is what /purchase/sms takes.
No key needed
The whole tree is public. Call it without a key and you get the public list prices, limited to 10 requests per second per IP address. Send your key and every price is your account's own — what an order will actually charge you — under your account's limit. A key that is wrong is refused with 403 rather than ignored. See Authentication.
- 1 · Platforms
- 2 · Countries
- 3 · Providers
- 4 · Options
curl https://numestri.com/api/v1/catalog/platforms{
"success": 1,
"prices_at": "2026-10-14T01:40:00.000Z",
"count": 1326,
"next": "/api/v1/catalog/platforms/{platform}/countries",
"data": [
{ "id": "discord", "name": "Discord", "countries": 12, "from_price": "0.07", "from_price_cents": 7 }
]
}Field | Meaning |
|---|---|
| The platform id: |
| How many countries it is in stock in. |
| Its cheapest number, anywhere. |
curl https://numestri.com/api/v1/catalog/platforms/discord/countries{
"success": 1,
"prices_at": "2026-10-14T01:40:00.000Z",
"platform": "discord",
"count": 12,
"next": "/api/v1/catalog/platforms/discord/countries/{country}/providers",
"data": [
{ "id": "US", "name": "United States", "providers": 2, "from_price": "0.07", "from_price_cents": 7 }
]
}Field | Meaning |
|---|---|
| ISO 3166-1 alpha-2 code: |
| How many providers sell the platform there. |
| The cheapest number for the platform in this country. |
curl https://numestri.com/api/v1/catalog/platforms/discord/countries/US/providers{
"success": 1,
"prices_at": "2026-10-14T01:40:00.000Z",
"platform": "discord",
"country": "US",
"count": 2,
"next": "/api/v1/catalog/platforms/discord/countries/US/providers/{provider}/options",
"data": [
{ "id": "prv_k3Jd9QmXa2", "rank": 1, "options": 2, "from_price": "0.07", "from_price_cents": 7, "quality": null, "success_rate": null }
]
}Field | Meaning |
|---|---|
| The provider handle: |
|
|
| How many offers it has here. |
| Its delivery record for this platform in this country; |
curl https://numestri.com/api/v1/catalog/platforms/discord/countries/US/providers/prv_k3Jd9QmXa2/options{
"success": 1,
"prices_at": "2026-10-14T01:40:00.000Z",
"platform": "discord",
"country": "US",
"provider": "prv_k3Jd9QmXa2",
"count": 2,
"order": { "endpoint": "/api/v1/purchase/sms", "params": { "service": "discord", "country": "US", "option": "{option}" } },
"data": [
{ "option": "k3Jd9QmXa2Lp0cWe", "rank": 1, "price": "0.07", "price_cents": 7, "quality": null, "success_rate": null, "stock": "plenty" },
{ "option": "Vb7nR2sYq0Te4hUa", "rank": 2, "price": "0.10", "price_cents": 10, "quality": null, "success_rate": null, "stock": "plenty" }
]
}Field | Meaning |
|---|---|
| The id to send as |
| What one number costs. |
|
|
order spells out the /purchase/sms call that buys one of these.
Ids: platform, country, provider, option
Id | Looks like | Stable? | Use it as |
|---|---|---|---|
Platform |
| Yes |
|
Country |
| Yes |
|
Provider |
| Yes — the same provider keeps its handle across platforms, countries and time |
|
Option | 8–32 letters, digits, | For as long as that offer exists |
|
Both handles are opaque. A provider handle says nothing about who is behind it, and an option id is specific to one platform and country — the same offer elsewhere has a different id — so don't parse them or build them yourself; copy them from the catalogue. An option that no longer exists is refused at order time with code OPTION_GONE.
Rules worth knowing
Only what is in stock. Every level lists only things you can buy right now. A choice with nothing under it is a
404whose message points one level up:{ "success": 0, "errors": [{ "message": "Nothing is in stock for \"discord\" in \"FR\". See /api/v1/catalog/platforms/discord/countries.", "param": "country" }] }Exact ids.
Telegram,tg,usandUnited Statesare not ids. In a catalogue path they find nothing and answer404; as aserviceorcountryparameter to/request/priceor/purchase/smsthey are refused with400. UsetelegramandUSas the catalogue lists them.Fresh. Prices and stock are refreshed about every two minutes;
prices_atsays when the prices you are looking at were read.Warming up. Right after a restart the catalogue may answer
503withPrices are being refreshed. Try again in a few seconds.— retry shortly.Shared numbers aren't in the tree. The tree lists private numbers. Whether a cheaper shared, public-inbox number is on offer for a platform and country is in the
shared_inboxfield of/request/price.
Caching with ETags
Every level carries an ETag and Cache-Control: private, max-age=30. Send the ETag back as If-None-Match, and while nothing at that level has changed you get a bodiless 304 instead of the list:
curl -i https://numestri.com/api/v1/catalog/platforms/discord/countries
# HTTP/1.1 200 OK
# ETag: W/"…"
curl -i https://numestri.com/api/v1/catalog/platforms/discord/countries -H 'If-None-Match: W/"…"'
# HTTP/1.1 304 Not ModifiedTags are weak and compared weakly:
W/"x"and"x"match.If-None-Matchmay hold a comma-separated list, or*.The tag changes whenever the prices behind the level may have changed — including when the prices that apply to you change — so a
304always means your copy is current.Public prices and your account's prices can have different tags. Keep the tag with the response it came from, and don't reuse an anonymous tag on a keyed request.
A request with
key=in the URL is never cacheable; send the key in a header if you want caching.
Overview call
GET /api/v1/catalog describes the tree and the three ways to order, so a client can discover it without hard-coding paths. It is public too.
{
"success": 1,
"prices_at": "2026-10-14T01:40:00.000Z",
"platforms_in_stock": 1326,
"tree": [
{ "level": 1, "list": "platforms", "path": "/api/v1/catalog/platforms" },
{ "level": 2, "list": "countries for a platform", "path": "/api/v1/catalog/platforms/{platform}/countries" },
{ "level": 3, "list": "providers for a platform in a country", "path": "/api/v1/catalog/platforms/{platform}/countries/{country}/providers" },
{ "level": 4, "list": "a provider's options", "path": "/api/v1/catalog/platforms/{platform}/countries/{country}/providers/{provider}/options" }
],
"ordering": {
"endpoint": "/api/v1/purchase/sms",
"choose_by": [
"service + country: the best option, failing over to the next",
"service + country + provider: that provider only, its best option first",
"service + country + option: exactly that option at its listed price, or nothing"
]
}
}Simpler public lists
If you only need names and ids — a dropdown, say — two flat lists keep the shape most verification APIs use. They take no key at all.
Endpoint | Returns |
|---|---|
|
|
|
|
ID is the exact id the ordering endpoints take. Both always show public prices.