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. Two actions answer JSON instead: getPrices and getPricesFull.
  • The HTTP status is 200 even for errorsBAD_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

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:

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) or our numeric idRU, ru, 185Where the number is from. An unknown country answers NO_NUMBERS, the same as a pair we do not sell.
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.

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.

237 services

service=NameCountriesFrom
1gb1GB253$0.160
99acres99acres253$0.150
112game112Game253$0.180
abcdABCD253$0.182
adityabirlaAdityabirla253$0.239
airtelAirtel253$0.150
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
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.209
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
indiamartIndiaMART253$0.081
indianoilIndianOil253$0.239
instagramInstagram253$0.090
instamatch365instamatch365253$0.090
instashopInstaShop253$0.627
irctcIRCTC (Indian Railways)253$0.122
iycIyc253$0.180
jarjar253$0.299
jiffyJiffy253$0.299
jioJio1$0.125
jiomartJioMart253$0.150
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
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
myjioMyJio1$0.140
myntraMyntra253$0.239
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$1.195
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.180
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.105
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.182
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.300
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.239
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.

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.

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.

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.

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_SERVICEgetNumbercheck service against getPrices
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 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