Overview
Two equivalent base paths, both GET and POST. Client libraries for this protocol hard-code the first, so an existing integration usually moves here by changing the base URL and the key alone.
https://smsprime.pro/stubs/handler_api.php
https://smsprime.pro/api
- Responses are plain text. Four actions answer JSON instead:
getPrices,getPricesFull,getCountriesandgetOperators. - The HTTP status is 200 even for errors —
BAD_KEYis a 200 whose body isBAD_KEY. Only the rate limit answers429. Parse the body, not the status. - Parameters are read from the query string and from a
POSTbody alike — send a given name in one place or the other, never in both. - Plain
http://answers308, but only after the request has crossed the network: treat a key sent that way as compromised.
Authentication
Every request carries api_key; a missing or wrong key answers BAD_KEY. The key is on your profile page, where reissuing it kills the old one immediately.
GET https://smsprime.pro/stubs/handler_api.php?api_key=YOUR_API_KEY&action=getBalance
Rate limit
Counted per second, set per account, 100 requests per second by default; your own figure is in your account, first card on the home page, under “API rate limit”. A change to it takes up to a minute to apply.
There are two counters, each allowed your full limit: getNumber with getStatus, and getBalance, getPrices, setStatus, getPricesFull, getCountries, getOperators and any unrecognised action together. Neither sees the other, so a 429 names the group you outran, and pacing both loops against one budget spends less than you are entitled to.
getPricesFull also has a second, much tighter limit of its own — 6 per minute, not per second, because that one answer is megabytes where every other action is a few hundred bytes. It is counted separately, so running it out costs you nothing on the calls above; details are in getPricesFull.
HTTP/2 429
Retry-After: 1
ERROR_RATE_LIMIT:1
The number after the colon is seconds to wait. The window is a fixed one second; then the counter is back to zero.
Which headers arrive depends on the action:
| Header | getBalance, getPrices, setStatus, unknown action | getNumber, getStatus |
|---|---|---|
X-Ratelimit-Limit | on every response | never |
X-Ratelimit-Remaining | on every response | never |
X-Ratelimit-Reset | only on a 429 | never |
Retry-After | only on a 429 | only on a 429 |
getBalance — current balance
?api_key=YOUR_API_KEY&action=getBalance
| Response | Meaning |
|---|---|
ACCESS_BALANCE:10000.000 | the balance, always with three decimals |
BAD_KEY | the key was rejected |
getNumber — request a number
?api_key=YOUR_API_KEY&action=getNumber&service=SERVICE&country=COUNTRY&operator=&maxPrice=
| Parameter | Format | Example | What it does |
|---|---|---|---|
service | our service code | vk, go | What the number is for. The codes are ours, not an industry standard — read them from getPrices. |
country | ISO country code (case does not matter), our numeric id, or the word any | RU, ru, 185, any | Where the number is from. An unknown country answers NO_NUMBERS, the same as a pair we do not sell. Send any (case does not matter, spaces are trimmed) and we gather every channel-and-country pair selling this service, drop what you cannot afford and what is above your maxPrice, then draw at random from the rest — so a pair you cannot pay for never comes up. ⚠️ You are charged the price of the country that came up, NOT the cheapest: on vk the pairs run 0.120 to 0.300, and WRONG_MAX_PRICE:0.120 is the floor of the set, not a quote. Name a ceiling with maxPrice and pairs above it never enter the draw. The reply keeps its usual shape, so read the country off the number itself. Only the word any means this: all, auto, 1000, 0, an empty value and a duplicated country all stay NO_NUMBERS. |
operator | channel number, a whole number from 1 (the examples show the shape, not a live channel) | 7, 42 | Optional. A channel is one of our supply routes, each with its own price. Name one and you are charged that channel's price, known before you order. Leave it out and we draw at random and charge the drawn channel's price — never above the cost getPrices gives without an operator, often below. The word any, in any case, means the same as leaving it out. Anything else that is not a channel you can order from today answers BAD_OPERATOR, as does sending it twice. |
maxPrice | amount, up to three decimals | 0.300 | Optional. The most you agree to pay for this activation. Channels dearer than this never enter the draw, so you are charged no more than you named. If none is left the answer is WRONG_MAX_PRICE:<cheapest that would work>. Omit it and every channel you can afford takes part, as before. |
An empty operator, and the word any, are exactly the same as leaving it out; channels are below.
country=any — «any country you have». Case does not matter (ANY, Any) and surrounding spaces are trimmed.
How the country is picked. We gather every channel-and-country pair that sells this service, drop the ones you cannot afford and the ones above your
maxPrice, and draw at random from what is left — evenly across pairs, so a country served by six channels comes up twice as often as one served by three. A pair you cannot pay for is dropped before the draw, never after: any cannot hand you a number you cannot afford.
⚠️ You are charged the price of the country that came up — not the cheapest one. This is the easy mistake. Live numbers for
service=vk (1 Sep 2026, 765 pairs across 253 countries): cheapest 0.120, typical 0.150, dearest 0.300; Russia alone is 0.240. Send country=any&maxPrice=0.001 and you get WRONG_MAX_PRICE:0.120 — that is the floor of the whole set, a hint, not a quote. The order itself may cost 0.300, two and a half times more. Want a ceiling? Name it: country=any&maxPrice=0.150 — pairs above the limit never enter the draw, so you are never charged more than you named. The spread differs from service to service, and the wider it is the more you want the limit; maxPrice=0.001 shows you the floor for any of them without buying anything.
⚠️ The reply is unchanged —
ACCESS_NUMBER:<id>:<number>, exactly as for every other request, so there is nothing to change in your code. We deliberately do not append the country code: parsers commonly take everything after the second colon as the number, and an extra field would end up inside it. Read the country off the number itself — there is no separate field, action or hint for it, and none is planned.
Only the word
any means this. 1000 (a number, and no country of ours carries it), all, auto, anyone, any1, an empty value, a missing parameter, a duplicated country, and lookalikes in other alphabets all stay NO_NUMBERS, exactly as before.| Response | Meaning |
|---|---|
ACCESS_NUMBER:<id>:<number> | a number was issued; id is the activation id used by every other call. Example: ACCESS_NUMBER:1:79997114088 |
NO_NUMBERS | No number is available. Also returned when the service and country pair is not one we sell, and when the country code is unknown |
NO_BALANCE | not enough funds — the account is empty or short for this order |
NO_BALANCE:RESERVED:<amount> | you do have funds, but that amount is held by your own activations that are still open. No top-up needed, and retrying will not change the answer: it is released when they finish or are cancelled. The word before the colon is unchanged, so prefix-based parsing keeps working |
BAD_SERVICE | no such service — and only that |
BAD_OPERATOR | the operator value is neither a channel you can order from today nor the word any, or the parameter was sent more than once |
BANNED | ordering is switched off for your account. Your key is valid and your balance is untouched: getBalance, getPrices, setStatus and getStatus keep working, and numbers you already ordered still deliver their codes. Only new orders are refused, and repeating will not change it — contact support |
BAD_KEY | the key was rejected |
NO_NUMBERS deliberately does not distinguish causes — out of stock, upstream silent, upstream broken, or a pair we do not sell all answer the same, because the difference between replies would let anyone work out whose numbers we resell. It is a normal working reply, not an incident, and on rare pairs the usual one.
How long the number lives
Five minutes, the same for every activation, whichever upstream served it. On expiry the activation is cancelled automatically and costs nothing. The closure reaches upstream six to seven minutes after the order — a grace minute past the deadline, so an SMS arriving in the last seconds is not lost, plus a sweeper that runs once a minute. The reliable close signal is STATUS_CANCEL, not your own clock.
Services — what to put in service=
Every code below is a valid service value. Codes are lower case and stable — once issued, a code is not renamed, because it is what your integration sends. The name beside it is only a label for people and may be reworded.
From is the cheapest a pair of this service can cost you, across all countries and all channels. It is a floor, not a quote: the price of a particular country is in getPrices, and without operator what you are charged is decided by the draw (see Channels).
getPrices to discover service codes. It answers with every pair we sell — around two megabytes — because it is a price list, not a catalogue. The same list you see here is served as JSON too, in one small request: GET https://smsprime.pro/catalog/services, no key needed.240 services
| service= | Name | Countries | From |
|---|---|---|---|
1gb | 1GB | 253 | $0.160 |
99acres | 99acres | 253 | $0.150 |
112game | 112Game | 253 | $0.180 |
abcd | ABCD | 253 | $0.182 |
adityabirla | Adityabirla | 253 | $0.239 |
airtel | Airtel | 253 | $0.120 |
aisensy | AiSensy | 253 | $0.896 |
ajio | AJIO | 253 | $0.150 |
algida | Algida | 253 | $0.167 |
amazon | Amazon | 253 | $0.090 |
awsamazon | Amazon Web Services | 253 | $0.090 |
anibisch | Anibis.ch | 253 | $0.986 |
aol | AOL Mail | 253 | $0.299 |
apple | Apple | 253 | $0.598 |
ashleymadison | Ashley Madison | 253 | $0.239 |
astrochat | Astrochat | 253 | $0.299 |
autotrader | AutoTrader | 253 | $0.806 |
bajajgeneral | BajajGeneral | 253 | $0.180 |
bharat | Bharat | 253 | $0.213 |
bharatpe | BharatPe | 253 | $0.150 |
bigbasket | BigBasket | 253 | $0.150 |
binbin | BinBin | 253 | $0.167 |
bitaksi | Bitaksi | 253 | $0.167 |
bitclout | BitClout | 253 | $0.239 |
blablacar | BlaBlaCar | 253 | $0.150 |
blinkit | Blinkit | 253 | $0.090 |
botim | Botim | 253 | $0.400 |
brevistay | Brevistay | 253 | $0.239 |
bumble | Bumble | 253 | $0.180 |
carrefour | Carrefour | 253 | $0.806 |
cashkaro | CashKaro | 253 | $0.060 |
chatgptopenai | ChatGPT (OpenAI) | 253 | $0.150 |
citymall | CityMall | 253 | $0.150 |
civo | Civo | 253 | $0.448 |
claudeai | Claude | 253 | $0.180 |
come | Come | 253 | $0.182 |
cricket | Cricket | 253 | $0.213 |
daha | Daha | 253 | $0.167 |
dahadaha | Dahadaha | 253 | $0.448 |
dealshare | DealShare | 253 | $0.180 |
digicredit | digicredit | 253 | $0.120 |
discord | Discord | 253 | $0.359 |
dotandkey | Dotandkey | 253 | $0.180 |
dragontiger | dragontiger | 253 | $0.120 |
dream11 | Dream11 | 253 | $0.182 |
drivebison | Drivebison | 253 | $0.239 |
ebay | eBay | 253 | $0.239 |
fb | 253 | $0.108 | |
fatafat | Fatafat | 253 | $0.299 |
financebuddha | FinanceBuddha | 253 | $0.180 |
flipkart | Flipkart | 253 | $0.150 |
flot | flot | 253 | $0.150 |
freehit | Freehit | 253 | $0.150 |
frnd | Frnd | 253 | $0.120 |
funcrash | FunCrash | 253 | $0.209 |
gharsoaps | GharSoaps | 253 | $0.180 |
go | Gmail | 253 | $0.110 |
gms | GMS | 253 | $0.280 |
gnc | GNC | 253 | $0.299 |
gochat | GoChat | 253 | $0.299 |
google | 253 | $0.239 | |
googlechat | Google Chat | 253 | $0.209 |
googlepay | Google Pay | 253 | $0.090 |
googlevoice | Google Voice | 253 | $0.299 |
grandiose | Grandiose | 253 | $0.598 |
hayatpay | HayatPay | 253 | $0.090 |
hepsiburada | Hepsiburada | 253 | $0.239 |
hfm | HFM | 253 | $0.359 |
hop | Hop | 253 | $0.167 |
hopi | Hopi | 253 | $0.239 |
horlicks | Horlicks | 253 | $0.150 |
icash | icash | 253 | $0.150 |
idealista | Idealista | 253 | $0.239 |
imo | imo | 253 | $0.100 |
indiamart | IndiaMART | 253 | $0.081 |
indianoil | IndianOil | 253 | $0.239 |
ig | 253 | $0.090 | |
instamatch365 | instamatch365 | 253 | $0.090 |
instashop | InstaShop | 253 | $0.627 |
irctc | IRCTC (Indian Railways) | 253 | $0.122 |
iyc | Iyc | 253 | $0.180 |
jar | jar | 253 | $0.299 |
jiffy | Jiffy | 253 | $0.299 |
jio | Jio | 253 | $0.125 |
jiomart | JioMart | 253 | $0.105 |
kakaotalk | KakaoTalk | 253 | $0.448 |
kazandirio | KazandiRio | 253 | $0.167 |
kgen | KGEN | 253 | $0.150 |
kodbende | Kodbende | 253 | $0.329 |
kucoin | KuCoin | 253 | $0.150 |
kufar | Kufar | 253 | $1.195 |
kuper | Kuper | 253 | $0.239 |
learnpass | LearnPass | 253 | $0.150 |
licious | Licious | 253 | $0.150 |
line | LINE | 253 | $0.359 |
linkedin | 253 | $0.239 | |
lpggasbooking | LPGGasBooking | 253 | $0.195 |
lptracker | Lptracker | 253 | $0.150 |
magicpin | magicpin | 253 | $0.299 |
magnit | Magnit | 1 | $0.120 |
makemytrip | MakeMyTrip | 253 | $0.280 |
mamba | Mamba | 253 | $0.180 |
marktplaats | Marktplaats | 253 | $0.150 |
masterai | MasterAI | 253 | $0.239 |
match | Match.com | 253 | $0.239 |
meesho | Meesho | 253 | $0.090 |
mera | Mera | 253 | $0.150 |
microsoft | Microsoft | 253 | $0.299 |
migros | Migros | 253 | $0.239 |
milkbasket | Milkbasket | 253 | $0.239 |
mobikwik | MobiKwik | 253 | $0.150 |
moneycontrol | Moneycontrol | 253 | $0.150 |
moneyfalls | Moneyfalls | 253 | $0.239 |
moneylion | Moneylion | 253 | $0.150 |
mospay | Mospay | 253 | $0.120 |
myhpgas | Myhpgas | 253 | $0.120 |
myjio | MyJio | 253 | $0.140 |
myntra | Myntra | 253 | $0.152 |
narendramodi | Narendra Modi (NaMo) | 253 | $0.090 |
naver | Naver | 253 | $0.239 |
navi | Navi | 253 | $0.150 |
ncsoft | NCSOFT | 253 | $0.150 |
neonmoneytalks | NeonMoneyTalks | 253 | $0.299 |
netflix | Netflix | 253 | $0.051 |
nielsen | Nielsen | 253 | $0.150 |
nike | Nike | 253 | $0.359 |
noon | noon | 253 | $0.150 |
nxcomm | Nxcomm | 253 | $6.269 |
nykaa | Nykaa | 253 | $0.359 |
okcupid | OkCupid | 253 | $0.299 |
olacabs | Ola Cabs | 253 | $0.180 |
olx | OLX | 253 | $1.493 |
openai | OpenAI | 253 | $0.239 |
opera | Opera | 253 | $0.150 |
opet | Opet | 253 | $0.090 |
ourtim | Ourtim | 253 | $0.195 |
payoneer | Payoneer | 253 | $0.299 |
paypal | PayPal | 253 | $0.260 |
paysend | Paysend | 253 | $0.448 |
paytm | Paytm | 253 | $0.299 |
payzo | PAYZO | 253 | $0.120 |
pazarama | Pazarama | 253 | $1.583 |
perplexity | Perplexity | 253 | $0.180 |
phonepe | PhonePe | 253 | $0.150 |
picpay | PicPay | 253 | $0.299 |
playcric | playcric | 253 | $0.150 |
playkaro | playkaro | 253 | $0.090 |
playtime | PlayTime | 253 | $0.090 |
pof | Plenty of Fish | 253 | $0.299 |
porter | Porter | 253 | $0.152 |
practo | Practo | 253 | $0.150 |
praxo | Praxo | 253 | $0.180 |
predik | Predik | 253 | $0.150 |
premiumbandai | PREMIUMBANDAI | 253 | $0.150 |
pvr | PVR Cinemas | 253 | $0.120 |
qitaf | Qitaf | 253 | $0.299 |
qoneqt | Qoneqt | 253 | $0.165 |
railone | RailOne | 253 | $0.120 |
rajagames | Rajagames | 253 | $0.180 |
rapido | Rapido | 253 | $0.180 |
rcs | RCS | 253 | $0.455 |
redrobsignal | RedrobSignal | 253 | $0.180 |
rekommendie | Rekommendie | 253 | $0.090 |
research360 | Research360 | 253 | $0.120 |
ricardo | Ricardo | 253 | $0.598 |
ring4 | Ring4 | 253 | $0.182 |
rsc360 | RSC360 | 253 | $0.150 |
rummy | RUMMY | 253 | $0.120 |
rummy77 | Rummy77 | 253 | $0.209 |
rummynoble | Rummynoble | 253 | $0.091 |
rummyola | Rummyola | 253 | $0.120 |
samokat | Samokat | 253 | $0.239 |
sfa | SFA | 253 | $0.150 |
shopee | Shopee | 253 | $0.359 |
shopsy | Shopsy | 253 | $0.239 |
shriram | Shriram | 253 | $0.213 |
signal | Signal | 253 | $0.299 |
sisalfunclub | SisalFunClub | 253 | $0.896 |
skelbiu | Skelbiu | 253 | $0.508 |
snapchat | Snapchat | 253 | $0.060 |
speakx | SpeakX | 253 | $0.180 |
spincrush | Spincrush | 253 | $0.120 |
stir | Stir | 253 | $0.239 |
storymax | StoryMax | 253 | $0.299 |
storytv | StoryTV | 253 | $0.090 |
subito | Subito | 253 | $0.150 |
swiggy | Swiggy | 253 | $0.150 |
tatacliqfashion | Tata CLiQ Fashion | 253 | $0.150 |
tataneu | Tata Neu | 253 | $0.896 |
tazi | tazi | 253 | $0.150 |
telefon | Telefon | 253 | $0.213 |
tg | Telegram | 253 | $0.300 |
telekom | Telekom | 253 | $0.120 |
telephia | Telephia | 253 | $0.299 |
ticketmaster | Ticketmaster | 253 | $0.099 |
tiktok | TikTok | 253 | $0.070 |
tinder | Tinder | 253 | $0.239 |
trendyol | Trendyol | 253 | $0.090 |
trendyolgo | Trendyol GO | 253 | $0.598 |
truecaller | Truecaller | 253 | $0.180 |
truemoney | TrueMoney | 253 | $0.359 |
truthsocial | Truth Social | 253 | $0.150 |
tutti | Tutti | 253 | $0.239 |
twilio | Twilio | 253 | $0.299 |
twitter | Twitter (X) | 253 | $0.448 |
uber | Uber | 253 | $0.060 |
unicef | UNICEF | 253 | $0.180 |
vegas | Vegas | 253 | $0.180 |
venmo | Venmo | 253 | $0.150 |
vfs | VFS Global | 253 | $0.836 |
vfsglobal | VFS Global | 253 | $2.687 |
viber | Viber | 253 | $0.213 |
viewpoint | Viewpoint | 253 | $0.180 |
vinted | Vinted | 253 | $0.359 |
viralotv | ViraloTV | 253 | $0.180 |
vk | VK.com | 253 | $0.120 |
walmart | Walmart | 253 | $0.299 |
walmartvriddhi | Walmart Vriddhi | 253 | $0.239 |
wellsfargo | Wells Fargo | 253 | $1.792 |
whatnot | Whatnot | 253 | $0.024 |
wa | WhatsApp (USA in stock) | 253 | $0.400 |
wildberries | Wildberries | 253 | $0.030 |
wingo | WinGo | 253 | $0.299 |
winmatch | WinMatch | 253 | $0.090 |
winmate | Winmate | 253 | $0.239 |
winzo | WinZO | 253 | $0.182 |
woohoo | Woohoo | 253 | $0.150 |
yahoo | Yahoo | 253 | $0.180 |
yandex | Yandex | 253 | $0.182 |
yani | Yani | 253 | $0.090 |
yemeksepeti | Yemeksepeti | 253 | $0.239 |
yn777 | YN777 | 253 | $0.239 |
yono777 | Yono777 | 253 | $0.120 |
yonorummy | YonoRummy | 253 | $0.121 |
yonovipv | yonovipv | 253 | $0.150 |
youtube | YouTube | 253 | $0.239 |
zepto | Zepto | 253 | $0.180 |
zoho | Zoho | 253 | $0.120 |
zomato | Zomato | 253 | $0.180 |
zupee | Zupee | 253 | $0.152 |
Country codes — what to put in country=
Two letters, ISO 3166-1 alpha-2, case does not matter: country=RU and country=ru are the same request. An unknown code answers NO_NUMBERS, the same reply as a pair we do not sell — the two are deliberately indistinguishable.
Or the word any, which means «any country you have» — see the note under getNumber for what it charges. The reply does not change shape. It is a word on purpose: a magic number would sit in the same numbering as real countries, where a value carried over from another service is already very likely to be a real country here, just not the one you meant. 1000 is not a country of ours, so it answers NO_NUMBERS like any other unknown number.
AA Azores, MI Madeira, IC Canary Islands, TI Tahiti. They behave like any other code.Which countries a service sells is per service, so it is not a single list to print here — there are 253 of them, and the answer differs by service. Fetch the one you need, no key required:
GET https://smsprime.pro/catalog/vk/countries
It answers JSON: iso2, the country name, and the price range for that pair. The picker on our site walks the same route, so what you get is exactly what a person sees. If you would rather choose by hand, the same lists are on the services pages.
country= also takes a number — ours, one per country, listed by getCountries. The two forms are interchangeable, so nothing here changes if you send ISO codes; read the warning under that action before you send numbers.
Channels — choosing a supply route
A channel is one of our supply routes. The same service and country pair often reaches you through several, and they are not interchangeable: each has its own price, and each may or may not be able to serve a given order. You name one by number in the operator parameter, which getNumber and getPrices both take.
Elsewhere in this protocol operator names a mobile network, and an unknown value is usually ignored. Here it is a channel number, so a network name carried over from another service answers BAD_OPERATOR instead of quietly charging you for a route you did not choose.
One word is accepted beside a number: any, in upper case, lower case or mixed. It means “no preference” and behaves in every respect as if the parameter had been left out — same draw, same price rule, same replies. Send it if your client insists on filling the field in. It is the only such word: all, auto, a network name or a near miss such as anyone still answers BAD_OPERATOR, because guessing what you meant would charge you for a route you did not choose.
Leaving operator out: same request, price in a range
Omit it, send it empty, or send any, and the request behaves as it did before channels existed — same parameters, same replies, same set of pairs. You are charged the price of the channel we drew, uniformly at random among those serving your pair, so two identical requests can cost different amounts. The channel that served you is in your order history.
operator the published price is a ceiling, not a fixed charge. The figure getPrices gives for a pair is the most it can cost you — the maximum across its channels. You will often be charged less and never more, and you cannot know how much until the order is placed. No code change is needed for this; what changed, on 7 August 2026, is the money.Channels you cannot afford are removed before the draw, so a small balance is served only by the cheaper ones, and NO_BALANCE comes back only when no channel at all is within reach — not when merely the dearest is.
Name a channel and none of that applies: you pay that channel’s price, known before you order, and it is the only channel we try — no fall-through to a neighbour, no retry inside the request, and an empty result is NO_NUMBERS. A retry carrying the same operator stays on that channel at that price; without operator every attempt is a fresh draw.
Capping the price: maxPrice
Without operator the draw decides what you pay, and the spread can be wide — a pair whose channels run from $0.300 to $1.364 will sometimes charge you four times the cheapest. maxPrice is your ceiling: channels dearer than the amount you name never enter the draw, so you are charged no more than you said. Everything else about the request is unchanged.
GET /stubs/handler_api.php?api_key=…&action=getNumber&service=vk&country=RU&maxPrice=0.300
The limit is inclusive: maxPrice=0.300 buys a 0.300 channel. Amounts take up to three decimals, the same as every price we print; a fourth is refused rather than rounded, because rounding one way tightens your limit and the other way loosens it, and neither is ours to choose.
WRONG_MAX_PRICE:<amount> — and the amount is the cheapest that would have worked, so you never have to guess your way up. It is a different reply from NO_NUMBERS on purpose: repeating the same request with the same limit will never succeed, whereas NO_NUMBERS is worth retrying. It is not NO_BALANCE either — your money is fine, your ceiling is not.The bare token WRONG_MAX_PRICE, with no amount, means the value itself was unusable: not a number, negative, more than three decimals, sent twice, or sent as an array such as maxPrice[]=0.300. That last one is refused, unlike operator[]=2 which is ignored: an unread operator means “no channel chosen”, but an unread maxPrice would mean “charge me anything” — in the very request where you asked us not to. No amount is suggested there, because we did not understand what you asked for. Money comes first: if you cannot afford any channel at all, the answer is NO_BALANCE even when a limit is also in play — top up first, then worry about the ceiling.
With operator the two work together: name a channel dearer than your limit and the order is refused, not silently charged. Both numbers came from you, and we honour both or neither.
When operator is refused
BAD_OPERATOR is about the parameter, not about the pair. It is decided before we look at the service and country at all, which is why repeating never changes it. A channel that is switched on but has no number for your pair answers NO_NUMBERS, and NO_NUMBERS with operator=2 means only that this request got no number — not that channel 2 is empty, not that another channel would answer differently, not that the pair is unsold. Trying every channel in turn tells you no more than trying one of them twice.Three causes answer BAD_OPERATOR and are deliberately indistinguishable: a number we never issued, one switched off, and one switched on but selling nothing anywhere. No timing or header difference separates them.
Spaces and leading zeros are trimmed, so operator=%202%20 and operator=007 order channels 2 and 7. The same trimming happens before any is recognised, so operator=%20ANY%20 is that word; but it has to be the whole value, and the letters have to be the plain ASCII ones — anyone, any1 and a look-alike typed in another alphabet are all BAD_OPERATOR. Sending operator twice is refused with BAD_OPERATOR even when both values are identical — decided on the raw request, before the value is parsed, so watch for an HTTP client that appends a default to a URL already carrying one; two copies of any are refused just the same. Anything that is not a plain scalar, such as operator[]=2, is disregarded rather than refused, and the order proceeds without a channel.
Which channels exist
Channel numbers come from us: they are on the site where you order, and you can discover them over the API. getPrices with an operator answers with a price list when that number is a channel you can order from, and BAD_OPERATOR otherwise — walking the numbers is a supported way to find out which.
For the channels that can serve a particular service and country, ask getOperators instead — walking operator tells you which channels exist, not which ones your order can use, and those are different questions: a channel that exists but does not serve your pair answers NO_NUMBERS, not BAD_OPERATOR.
Walk 1 to 99. That is the whole range we issue from, handed out at random, so a walk that stops at 9 or at 40 simply misses channels. Values above 99 are accepted by the parser but are not numbers we hand out, and 32768 and above are refused outright.
A channel that sells nothing anywhere is not a channel: to answer at all, the route behind a number has to be selling at least one service in at least one country. That is about the channel as a whole, never about your pair — a listed channel can still have nothing for the pair you want, which is an ordinary NO_NUMBERS. Naming a channel never narrows the catalogue either, so the walk tells you which channels you can order from and nothing more.
The channel number is stable
Numbers are stored, not computed, so switching a channel off never renumbers the others, and a switched-off channel stops answering rather than quietly becoming someone else. The one qualification: numbers no client is ordering through do get reassigned, so that the numbering says nothing about us. A number you use is left alone; one you wrote down and then left unused is not reserved for you, and if it is reassigned it will not start failing — it will start buying from a different channel at a different price. Check it against getPrices after a long gap.
getStatus — read the code
?api_key=YOUR_API_KEY&action=getStatus&id=ACTIVATION_ID
| Parameter | Format | Example | What it does |
|---|---|---|---|
id | the activation id from ACCESS_NUMBER | 42 | Which activation to ask about. An activation that is not yours answers NO_ACTIVATION, exactly like one that never existed. |
| Response | Meaning |
|---|---|
STATUS_WAIT_CODE | the number was issued, the SMS has not arrived — keep polling |
STATUS_OK:123456 | the code arrived; this is when you are charged, and the activation is closed |
STATUS_CANCEL | the activation was cancelled or expired |
NO_ACTIVATION | no such activation, or it belongs to someone else |
Poll once every 2–5 seconds. STATUS_WAIT_CODE is partly served from an 8-second cache, so polling faster than once a second cannot make the code arrive sooner — it only spends your rate limit.
setStatus — confirm or cancel
?api_key=YOUR_API_KEY&action=setStatus&id=ACTIVATION_ID&status=CODE
| Parameter | Format | Example | What it does |
|---|---|---|---|
id | the activation id from ACCESS_NUMBER | 42 | Which activation to close. The activation is looked up before status is read, so a wrong status on an unknown id answers NO_ACTIVATION. |
status | one of 6, 8, -1 | 8 | What to record: 6 acknowledges the code and changes nothing, 8 and -1 both cancel. Anything else answers BAD_ACTION and leaves the activation open. |
status | Meaning | Response |
|---|---|---|
6 | acknowledge that you received the code | ACCESS_ACTIVATION |
8 | cancel the activation | ACCESS_CANCEL, or ACCESS_ACTIVATION if the code already arrived |
-1 | cancel the activation — exactly the same as 8 | ACCESS_CANCEL, or ACCESS_ACTIVATION if the code already arrived |
| anything else | 0, 1, 2, 3, -2, 7, 9 and the rest are not a cancellation and leave the activation open | BAD_ACTION |
An unknown activation, or one belonging to someone else, answers NO_ACTIVATION — and the activation is looked up before status is parsed, so an invalid status on an activation that does not exist answers NO_ACTIVATION, not BAD_ACTION.
-1 and 8 are equivalent — send either, the result is literally the same: same reply, same resulting state, same cancellation upstream, same idempotency on a repeat, no money moving either way, and someone else’s activation answering NO_ACTIVATION alike. Services speaking this protocol record a cancellation differently, so we accept both. Until 1 August 2026 only 8 worked and -1 answered BAD_ACTION, leaving the activation open until it expired — retest if you tested against us before that date.status=6 does not close the activation. It answers ACCESS_ACTIVATION and leaves the state exactly as it was; you are charged when the code arrives, at STATUS_OK, so sending it is optional and harmless. If your previous service used the other values for intermediate steps, drop them: between issuing the number and the code arriving there is no state worth reporting.
When to cancel
Cancelling by hand is not required — an unclaimed activation closes itself, and nothing is charged either way. But an explicit cancellation closes it on our side immediately and asks upstream to release the number, and an open activation holds the number: on rare destinations, ordering the same pair again may not succeed until it is closed. Cancelling is free until the code arrives: you are charged for the code, so cancelling before it adds no charge, and after it there is nothing left to cancel — the charge stands and is not refunded.
ACCESS_ACTIVATION, not ACCESS_CANCEL (since 10 August 2026). It is finished and charged, so there is nothing to cancel; the activation stays as it is and no money moves. Before that date it answered ACCESS_CANCEL like any other closed activation, and a cleanup loop cancelling everything still open could not tell a paid activation from an unpaid one — now it can. There is no dedicated token for this case in the protocol we speak, so we use its own word for a finished activation rather than invent one your client would fail to parse. Which code arrived, and whether it arrived at all, is still a question for getStatus.ACCESS_CANCEL means the activation is closed and nothing was charged for it. It does not promise the number was released upstream that second: if that request failed, the number is freed when its own lifetime runs out. A repeat on an already-cancelled activation is not an error and answers ACCESS_CANCEL again.getPrices — the price list
?api_key=YOUR_API_KEY&action=getPrices&operator=
| Parameter | Format | Example | What it does |
|---|---|---|---|
operator | channel number, a whole number from 1 (the examples show the shape, not a live channel) | 7, 42 | Optional. A channel is one of our supply routes, each with its own price. Name one and you are charged that channel's price, known before you order. Leave it out and we draw at random and charge the drawn channel's price — never above the cost getPrices gives without an operator, often below. The word any, in any case, means the same as leaving it out. Anything else that is not a channel you can order from today answers BAD_OPERATOR, as does sending it twice. |
The only action that answers with JSON. First-level key is the country in lower case, second is the service code.
{
"ru": {
"go": { "cost": 1.23, "count": 100 },
"vk": { "cost": 4.56, "count": 100 }
}
}
The numbers above are invented — read prices from getPrices itself, never from this page. The list is cached, so a price change shows up within a minute, and only pairs we sell appear in it.
Without operator, cost is a ceiling, not a fixed charge — the highest of the prices that pair’s channels charge (see Channels). With operator it is exact: the amount getNumber with the same operator would charge, pairs where that channel has no price of its own keeping the list price. The set of pairs is the same either way, including pairs that channel cannot serve, so nothing here says which channel serves which pair.
count is always the constant 100, for every pair — part of the protocol’s shape, not a stock figure, and nothing can be inferred from it. Elsewhere in the industry the same field is sometimes real.
An operator that is not a channel you can order from answers BAD_OPERATOR in plain text — the one case where this action does not answer JSON.
getPricesFull — the price list broken down by channel
?api_key=YOUR_API_KEY&action=getPricesFull
This action is ours, not part of the protocol. No other service speaking it has an action by this name, so a client library will not know it — you read this response with your own code. It takes no parameters at all, not even operator: every channel is already in the answer.
The shape extends getPrices rather than replacing it. First-level key is the country in lower case, second is the service code; cost and count mean exactly what they mean there, and channels is added beside them.
{
"ru": {
"vk": {
"cost": 4.56,
"count": 100,
"channels": { "7": 1.11, "42": 4.56 }
}
}
}
The numbers above are invented, as on getPrices — read prices from the action itself, never from this page.
channels maps the channel number — the same number you send as operator to getNumber (see Channels) — to the price that channel charges for this pair. It is an object, never a list, and a pair served by no numbered channel has it as {} rather than as an empty list.
cost stays the ceiling: the highest of the channel prices below it, which is what an order without operator can cost you at worst. Both readings of the same pair are in one answer, so nothing has to be joined across two requests taken at different moments.
The pairs are the same pairs getPrices returns, in the same order of keys — sorted, which also makes the answer compress far better. Only pairs we actually sell appear, and a channel appears on a pair only if it can serve it.
429 here may tell you to wait up to 60 seconds — every other action counts in one-second windows, and code written against that assumption will simply keep being refused. Send Accept-Encoding: gzip: compressed, the same answer is tens of kilobytes. The catalogue changes rarely, so fetch it, keep it, and refresh it on a schedule of minutes rather than seconds.getCountries — the country list
?api_key=YOUR_API_KEY&action=getCountries
Answers JSON: an object whose key is our number for a country and whose value describes it. It takes no parameters, and it needs a key like every other action.
{
"185": {
"id": 185,
"rus": "Russian Federation",
"eng": "Russian Federation",
"chn": "Russian Federation",
"visible": 1,
"retry": 0,
"rent": 0,
"multiService": 0
}
}
country=7 is a perfectly valid request and orders a number in Andorra. There is no country 0 here at all, so code that has 0 standing for Russia gets NO_NUMBERS and at least fails loudly. If your code sends numbers, take them from this action and match them by name. If it sends ISO codes, none of this touches you.What each field means here:
| Field | What it means |
|---|---|
id | Our number for the country, and the same value as the key it sits under. This is exactly what country= takes: country=185 and country=RU are the same request. |
rus, eng, chn | The name of the country. We hold one name per country and it is English, so all three fields carry it. The protocol keeps three slots for three languages; we have no translations and will not invent any, and leaving the fields out or empty would only hand your code a blank where a country name belongs. |
visible | 1 when the country is in the price list — at least one of its pairs is on public sale right now; 0 when none is. It is the very same set of countries getPrices returns, so the two answers cannot disagree. The price list is a subset of what can be ordered, so 0 means we are not listing it, not that an order is certain to be refused. |
retry | Always 0. Asking for a second SMS on a number already issued is not something we do. |
rent | Always 0. We sell single activations; renting a number for a period is not on offer. |
multiService | Always 0. One number carries one service here. |
Every country we know is listed, including the ones we do not sell today — those carry visible: 0 instead of being left out. That way the list decodes any of our numbers you ever meet, and a country that starts selling tomorrow costs you nothing to pick up. A country we have switched off entirely is not listed at all, because country= would not take its number either.
The answer is small — tens of kilobytes for all 253 countries — and it changes rarely, while the visible flags follow the catalogue within a minute, exactly like getPrices. Fetch it when your program starts, not once per order.
getOperators — which channels serve a service
?api_key=YOUR_API_KEY&action=getOperators&service=SERVICE&country=COUNTRY
| Parameter | Format | Example | What it does |
|---|---|---|---|
service | our service code | vk, go | Required here, unlike the protocol this borrows its shape from: our channels differ from service to service, so a country-wide list would name channels your order cannot use. A code we do not know answers BAD_SERVICE. |
country | ISO country code (case does not matter) or our numeric id | RU, ru, 185 | Optional. Leave it out and every country we sell this service in is listed. One we do not comes back empty, not as an error. |
Answers JSON. countryOperators maps our number for a country to the channel numbers that can serve this service there — the same numbers you send as operator to getNumber (see Channels).
{
"status": "success",
"countryOperators": {
"185": ["19", "36", "44", "55", "88", "93"]
}
}
operator is a supply channel, not a mobile network — and that is why service is required here. Elsewhere in the industry this action takes only a country, because there an operator is a network and a network does not depend on what you are verifying. Ours does: a channel belongs to a service and a country together. Listing a country’s channels across all services would name, on our current catalogue, more than three wrong channels for every right one — and every wrong one answers NO_NUMBERS when you order with it. So there is no country-wide list here, and asking without service answers BAD_SERVICE rather than guessing.Leave country out and every country we sell this service in is listed — that answer is a few kilobytes, so the parameter saves you bandwidth rather than making the question answerable. It takes an ISO code or our number, exactly like country= at getNumber, and the key in the answer is always our number: ask for RU and the answer is filed under 185. Read the warning under getCountries before you send numbers of your own.
A country we do not sell this service in is simply not in the answer, and so is a country we do not know at all — the answer is then {}, not an error token. There is no refusal token for a country in this protocol, and NO_NUMBERS would be the wrong one to borrow: it means “try again later”, and this will not become true later.
A country listed with an empty array is a real answer, not a bug: we sell the pair, and no numbered channel serves it. Order it without operator and it works — there is simply nothing to name.
Channels we do not publish are not here either. A channel held back for a negotiated price does not appear, though it still serves an order that names it; a channel withdrawn from a pair appears nowhere and serves no one. The list is exactly the channels keys of the same pair in getPricesFull — the same rule read two ways, and it cannot disagree with itself.
The answer is small and changes rarely, and it follows the catalogue within a minute, exactly like getPrices. Fetch it when you set up a service, not once per order.
All responses
| Response | Where it appears | What to do |
|---|---|---|
ACCESS_BALANCE:<amount> | getBalance | — |
ACCESS_NUMBER:<id>:<number> | getNumber | poll getStatus |
ACCESS_ACTIVATION | setStatus 6 | — |
ACCESS_CANCEL | setStatus 8 or -1 | — |
STATUS_WAIT_CODE | getStatus | keep polling |
STATUS_OK:<code> | getStatus | take the code; the activation is closed |
STATUS_CANCEL | getStatus | closed — order again if you still need a number |
NO_NUMBERS | getNumber | retry later, or choose another country |
NO_BALANCE | getNumber | top up your account |
NO_BALANCE:RESERVED:<amount> | getNumber | wait for your open activations or cancel them; topping up is optional |
NO_ACTIVATION | getStatus, setStatus | check the id |
BAD_SERVICE | getNumber, getOperators | check service against getPrices; at getOperators it also means the parameter was left out, which that action does not allow |
BAD_OPERATOR | getNumber, getPrices | check operator; order without it, or with operator=any, to take any channel |
BANNED | getNumber | ordering is switched off for your account — the key and the balance are fine, and getStatus still delivers codes for numbers you already ordered; repeating will not help, contact support |
BAD_KEY | any | check the key |
BAD_ACTION | any | check action / status |
ERROR_RATE_LIMIT:<sec> | any, HTTP 429 | wait the given number of seconds — on getPricesFull that can be up to 60 |
ERROR_SQL | any | a temporary fault on our side, not a problem with your request — retry after a few seconds, and tell us if it keeps happening |
There are exactly eight actions: getBalance, getNumber, getStatus, setStatus, getPrices, getPricesFull, getCountries, getOperators. Any other value of action answers BAD_ACTION — including a near miss such as getpricesfull, since the name is matched exactly. Seven of the eight are the protocol’s; getPricesFull is ours. getOperators keeps the protocol’s name and shape but requires service, because our operator is a channel and channels differ per service (see getOperators). Treat an unknown response as an error, not a success — the set of tokens may grow.
Quick start
KEY=$SMS_PRIME_API_KEY
BASE=https://smsprime.pro/stubs/handler_api.php
curl -s "$BASE?api_key=$KEY&action=getBalance"
# ACCESS_BALANCE:10000.000
curl -s "$BASE?api_key=$KEY&action=getNumber&service=vk&country=RU"
# ACCESS_NUMBER:42:79991234567
curl -s "$BASE?api_key=$KEY&action=getStatus&id=42"
# STATUS_WAIT_CODE — repeat every 2-5 s until STATUS_OK:<code> or STATUS_CANCEL
curl -s "$BASE?api_key=$KEY&action=setStatus&id=42&status=8"
# ACCESS_CANCEL