Developer documentation

HTTP API for SMS Verification

Check your balance and prices, request a number, follow an activation and cancel it — from your own application, over plain HTTP.

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, getCountries and getOperators.
  • The HTTP status is 200 even for errors — BAD_KEY is a 200 whose body is BAD_KEY. Only the rate limit answers 429. Parse the body, not the status.
  • Parameters are read from the query string and from a POST body alike — send a given name in one place or the other, never in both.
  • Plain http:// answers 308, 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
Extra keys (sk_…, made on the API keys page) act for your whole account, not for a part of it. An extra key spends your balance, getBalance under it shows your balance, and it shares your rate limit. getStatus and setStatus under it accept any order of your account — including orders placed with your main key or with another extra key — so whoever holds it can read your codes and cancel your orders. The key narrows only what gets bought: hand it over only as you would your main key. Disabled or deleted, it answers BAD_KEY.

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:

HeadergetBalance, getPrices, setStatus, unknown actiongetNumber, getStatus
X-Ratelimit-Limiton every responsenever
X-Ratelimit-Remainingon every responsenever
X-Ratelimit-Resetonly on a 429never
Retry-Afteronly on a 429only on a 429

getBalance — current balance

?api_key=YOUR_API_KEY&action=getBalance
ResponseMeaning
ACCESS_BALANCE:10000.000the balance, always with three decimals
BAD_KEYthe key was rejected

getNumber — request a number

?api_key=YOUR_API_KEY&action=getNumber&service=SERVICE&country=COUNTRY&operator=&maxPrice=
ParameterFormatExampleWhat it does
serviceour service codevk, goWhat the number is for. The codes are ours, not an industry standard — read them from getPrices.
countryISO country code (case does not matter), our numeric id, or the word anyRU, ru, 185, anyWhere 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.
operatorchannel number, a whole number from 1 (the examples show the shape, not a live channel)7, 42Optional. 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.
maxPriceamount, up to three decimals0.300Optional. 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.
ResponseMeaning
ACCESS_NUMBER:<id>:<number>a number was issued; id is the activation id used by every other call. Example: ACCESS_NUMBER:1:79997114088
NO_NUMBERSNo number is available. Also returned when the service and country pair is not one we sell, and when the country code is unknown
NO_BALANCEnot 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_SERVICEno such service — and only that
BAD_OPERATORthe operator value is neither a channel you can order from today nor the word any, or the parameter was sent more than once
BANNEDordering 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_KEYthe 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.

The grace minute still charges: a code that turns up in it is delivered to you and billed. Expiry closes an activation that got nothing — it does not cancel one that did. If you have stopped wanting the number, cancel it rather than let it lapse: the five-minute mark is when the number stops being yours, not a guarantee that no money will move afterwards.

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

Do not walk 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=NameCountriesFrom
1gb1GB253$0.160
99acres99acres253$0.150
112game112Game253$0.180
abcdABCD253$0.182
adityabirlaAdityabirla253$0.239
airtelAirtel253$0.120
aisensyAiSensy253$0.896
ajioAJIO253$0.150
algidaAlgida253$0.167
amazonAmazon253$0.090
awsamazonAmazon Web Services253$0.090
anibischAnibis.ch253$0.986
aolAOL Mail253$0.299
appleApple253$0.598
ashleymadisonAshley Madison253$0.239
astrochatAstrochat253$0.299
autotraderAutoTrader253$0.806
bajajgeneralBajajGeneral253$0.180
bharatBharat253$0.213
bharatpeBharatPe253$0.150
bigbasketBigBasket253$0.150
binbinBinBin253$0.167
bitaksiBitaksi253$0.167
bitcloutBitClout253$0.239
blablacarBlaBlaCar253$0.150
blinkitBlinkit253$0.090
botimBotim253$0.400
brevistayBrevistay253$0.239
bumbleBumble253$0.180
carrefourCarrefour253$0.806
cashkaroCashKaro253$0.060
chatgptopenaiChatGPT (OpenAI)253$0.150
citymallCityMall253$0.150
civoCivo253$0.448
claudeaiClaude253$0.180
comeCome253$0.182
cricketCricket253$0.213
dahaDaha253$0.167
dahadahaDahadaha253$0.448
dealshareDealShare253$0.180
digicreditdigicredit253$0.120
discordDiscord253$0.359
dotandkeyDotandkey253$0.180
dragontigerdragontiger253$0.120
dream11Dream11253$0.182
drivebisonDrivebison253$0.239
ebayeBay253$0.239
fbFacebook253$0.108
fatafatFatafat253$0.299
financebuddhaFinanceBuddha253$0.180
flipkartFlipkart253$0.150
flotflot253$0.150
freehitFreehit253$0.150
frndFrnd253$0.120
funcrashFunCrash253$0.209
gharsoapsGharSoaps253$0.180
goGmail253$0.110
gmsGMS253$0.280
gncGNC253$0.299
gochatGoChat253$0.299
googleGoogle253$0.239
googlechatGoogle Chat253$0.209
googlepayGoogle Pay253$0.090
googlevoiceGoogle Voice253$0.299
grandioseGrandiose253$0.598
hayatpayHayatPay253$0.090
hepsiburadaHepsiburada253$0.239
hfmHFM253$0.359
hopHop253$0.167
hopiHopi253$0.239
horlicksHorlicks253$0.150
icashicash253$0.150
idealistaIdealista253$0.239
imoimo253$0.100
indiamartIndiaMART253$0.081
indianoilIndianOil253$0.239
igInstagram253$0.090
instamatch365instamatch365253$0.090
instashopInstaShop253$0.627
irctcIRCTC (Indian Railways)253$0.122
iycIyc253$0.180
jarjar253$0.299
jiffyJiffy253$0.299
jioJio253$0.125
jiomartJioMart253$0.105
kakaotalkKakaoTalk253$0.448
kazandirioKazandiRio253$0.167
kgenKGEN253$0.150
kodbendeKodbende253$0.329
kucoinKuCoin253$0.150
kufarKufar253$1.195
kuperKuper253$0.239
learnpassLearnPass253$0.150
liciousLicious253$0.150
lineLINE253$0.359
linkedinLinkedIn253$0.239
lpggasbookingLPGGasBooking253$0.195
lptrackerLptracker253$0.150
magicpinmagicpin253$0.299
magnitMagnit1$0.120
makemytripMakeMyTrip253$0.280
mambaMamba253$0.180
marktplaatsMarktplaats253$0.150
masteraiMasterAI253$0.239
matchMatch.com253$0.239
meeshoMeesho253$0.090
meraMera253$0.150
microsoftMicrosoft253$0.299
migrosMigros253$0.239
milkbasketMilkbasket253$0.239
mobikwikMobiKwik253$0.150
moneycontrolMoneycontrol253$0.150
moneyfallsMoneyfalls253$0.239
moneylionMoneylion253$0.150
mospayMospay253$0.120
myhpgasMyhpgas253$0.120
myjioMyJio253$0.140
myntraMyntra253$0.152
narendramodiNarendra Modi (NaMo)253$0.090
naverNaver253$0.239
naviNavi253$0.150
ncsoftNCSOFT253$0.150
neonmoneytalksNeonMoneyTalks253$0.299
netflixNetflix253$0.051
nielsenNielsen253$0.150
nikeNike253$0.359
noonnoon253$0.150
nxcommNxcomm253$6.269
nykaaNykaa253$0.359
okcupidOkCupid253$0.299
olacabsOla Cabs253$0.180
olxOLX253$1.493
openaiOpenAI253$0.239
operaOpera253$0.150
opetOpet253$0.090
ourtimOurtim253$0.195
payoneerPayoneer253$0.299
paypalPayPal253$0.260
paysendPaysend253$0.448
paytmPaytm253$0.299
payzoPAYZO253$0.120
pazaramaPazarama253$1.583
perplexityPerplexity253$0.180
phonepePhonePe253$0.150
picpayPicPay253$0.299
playcricplaycric253$0.150
playkaroplaykaro253$0.090
playtimePlayTime253$0.090
pofPlenty of Fish253$0.299
porterPorter253$0.152
practoPracto253$0.150
praxoPraxo253$0.180
predikPredik253$0.150
premiumbandaiPREMIUMBANDAI253$0.150
pvrPVR Cinemas253$0.120
qitafQitaf253$0.299
qoneqtQoneqt253$0.165
railoneRailOne253$0.120
rajagamesRajagames253$0.180
rapidoRapido253$0.180
rcsRCS253$0.455
redrobsignalRedrobSignal253$0.180
rekommendieRekommendie253$0.090
research360Research360253$0.120
ricardoRicardo253$0.598
ring4Ring4253$0.182
rsc360RSC360253$0.150
rummyRUMMY253$0.120
rummy77Rummy77253$0.209
rummynobleRummynoble253$0.091
rummyolaRummyola253$0.120
samokatSamokat253$0.239
sfaSFA253$0.150
shopeeShopee253$0.359
shopsyShopsy253$0.239
shriramShriram253$0.213
signalSignal253$0.299
sisalfunclubSisalFunClub253$0.896
skelbiuSkelbiu253$0.508
snapchatSnapchat253$0.060
speakxSpeakX253$0.180
spincrushSpincrush253$0.120
stirStir253$0.239
storymaxStoryMax253$0.299
storytvStoryTV253$0.090
subitoSubito253$0.150
swiggySwiggy253$0.150
tatacliqfashionTata CLiQ Fashion253$0.150
tataneuTata Neu253$0.896
tazitazi253$0.150
telefonTelefon253$0.213
tgTelegram253$0.300
telekomTelekom253$0.120
telephiaTelephia253$0.299
ticketmasterTicketmaster253$0.099
tiktokTikTok253$0.070
tinderTinder253$0.239
trendyolTrendyol253$0.090
trendyolgoTrendyol GO253$0.598
truecallerTruecaller253$0.180
truemoneyTrueMoney253$0.359
truthsocialTruth Social253$0.150
tuttiTutti253$0.239
twilioTwilio253$0.299
twitterTwitter (X)253$0.448
uberUber253$0.060
unicefUNICEF253$0.180
vegasVegas253$0.180
venmoVenmo253$0.150
vfsVFS Global253$0.836
vfsglobalVFS Global253$2.687
viberViber253$0.213
viewpointViewpoint253$0.180
vintedVinted253$0.359
viralotvViraloTV253$0.180
vkVK.com253$0.120
walmartWalmart253$0.299
walmartvriddhiWalmart Vriddhi253$0.239
wellsfargoWells Fargo253$1.792
whatnotWhatnot253$0.024
waWhatsApp (USA in stock)253$0.400
wildberriesWildberries253$0.030
wingoWinGo253$0.299
winmatchWinMatch253$0.090
winmateWinmate253$0.239
winzoWinZO253$0.182
woohooWoohoo253$0.150
yahooYahoo253$0.180
yandexYandex253$0.182
yaniYani253$0.090
yemeksepetiYemeksepeti253$0.239
yn777YN777253$0.239
yono777Yono777253$0.120
yonorummyYonoRummy253$0.121
yonovipvyonovipv253$0.150
youtubeYouTube253$0.239
zeptoZepto253$0.180
zohoZoho253$0.120
zomatoZomato253$0.180
zupeeZupee253$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.

Four of our codes are regions, not ISO countries, and no standard list will have them: 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.

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

If nothing is left, the answer is 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
ParameterFormatExampleWhat it does
idthe activation id from ACCESS_NUMBER42Which activation to ask about. An activation that is not yours answers NO_ACTIVATION, exactly like one that never existed.
ResponseMeaning
STATUS_WAIT_CODEthe number was issued, the SMS has not arrived — keep polling
STATUS_OK:123456the code arrived; this is when you are charged, and the activation is closed
STATUS_CANCELthe activation was cancelled or expired
NO_ACTIVATIONno 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
ParameterFormatExampleWhat it does
idthe activation id from ACCESS_NUMBER42Which activation to close. The activation is looked up before status is read, so a wrong status on an unknown id answers NO_ACTIVATION.
statusone of 6, 8, -18What to record: 6 acknowledges the code and changes nothing, 8 and -1 both cancel. Anything else answers BAD_ACTION and leaves the activation open.
statusMeaningResponse
6acknowledge that you received the codeACCESS_ACTIVATION
8cancel the activationACCESS_CANCEL, or ACCESS_ACTIVATION if the code already arrived
-1cancel the activation — exactly the same as 8ACCESS_CANCEL, or ACCESS_ACTIVATION if the code already arrived
anything else0, 1, 2, 3, -2, 7, 9 and the rest are not a cancellation and leave the activation openBAD_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.

An activation whose code already arrived answers 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=
ParameterFormatExampleWhat it does
operatorchannel number, a whole number from 1 (the examples show the shape, not a live channel)7, 42Optional. 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.

This is the heavy one — treat it as a download, not as a poll. The uncompressed answer is several megabytes and takes about a second to build, so it has its own limit of 6 requests per minute and a 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
  }
}
These numbers are ours. They are not the numbers another service uses, and a wrong one is accepted rather than refused. Countries are numbered in this protocol too, and the numbering differs — but the ranges overlap, so a number carried over from elsewhere is very likely to be a real country here, just not the one you meant. 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:

FieldWhat it means
idOur 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, chnThe 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.
visible1 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.
retryAlways 0. Asking for a second SMS on a number already issued is not something we do.
rentAlways 0. We sell single activations; renting a number for a period is not on offer.
multiServiceAlways 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
ParameterFormatExampleWhat it does
serviceour service codevk, goRequired 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.
countryISO country code (case does not matter) or our numeric idRU, ru, 185Optional. 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"]
  }
}
Our 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

ResponseWhere it appearsWhat to do
ACCESS_BALANCE:<amount>getBalance—
ACCESS_NUMBER:<id>:<number>getNumberpoll getStatus
ACCESS_ACTIVATIONsetStatus 6—
ACCESS_CANCELsetStatus 8 or -1—
STATUS_WAIT_CODEgetStatuskeep polling
STATUS_OK:<code>getStatustake the code; the activation is closed
STATUS_CANCELgetStatusclosed — order again if you still need a number
NO_NUMBERSgetNumberretry later, or choose another country
NO_BALANCEgetNumbertop up your account
NO_BALANCE:RESERVED:<amount>getNumberwait for your open activations or cancel them; topping up is optional
NO_ACTIVATIONgetStatus, setStatuscheck the id
BAD_SERVICEgetNumber, getOperatorscheck service against getPrices; at getOperators it also means the parameter was left out, which that action does not allow
BAD_OPERATORgetNumber, getPricescheck operator; order without it, or with operator=any, to take any channel
BANNEDgetNumberordering 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_KEYanycheck the key
BAD_ACTIONanycheck action / status
ERROR_RATE_LIMIT:<sec>any, HTTP 429wait the given number of seconds — on getPricesFull that can be up to 60
ERROR_SQLanya 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