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. Two actions answer JSON instead:
getPricesandgetPricesFull. - 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 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) or our numeric id | RU, ru, 185 | Where the number is from. An unknown country answers NO_NUMBERS, the same as a pair we do not sell. |
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.
| 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.237 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.150 |
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 |
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.209 |
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 |
indiamart | IndiaMART | 253 | $0.081 |
indianoil | IndianOil | 253 | $0.239 |
instagram | 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 | 1 | $0.125 |
jiomart | JioMart | 253 | $0.150 |
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 |
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 | 1 | $0.140 |
myntra | Myntra | 253 | $0.239 |
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 | $1.195 |
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.180 |
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.105 |
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.182 |
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.300 |
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.239 |
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.
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.
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.
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.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 | check service against getPrices |
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 six actions: getBalance, getNumber, getStatus, setStatus, getPrices, getPricesFull. Any other value of action answers BAD_ACTION — including a near miss such as getpricesfull, since the name is matched exactly. Five of the six are the protocol’s; getPricesFull is ours, and channels added no action at all, since operator is a parameter. 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