2 Choose a country 145 countries

Select a country

3 Carrier and tariff

Name a platform and a country, in either order. The carrier and the tariff appear here.

For developers

API reference

Read the balance and the standing tariff for any service in any country straight from your own code. One key, carried in a header, and JSON coming back.

Base address

https://smsactivate.io/api/v1
Issue a key

First request #

JSON over HTTPS throughout, authenticated by one key you issue yourself. Nothing to install, nothing to sign, no sandbox to be granted — a key and curl will do.

  1. 1 Create a key Account menu, then API key, then create one. It appears once and only its fingerprint is retained, so there is no looking it up later.
  2. 2 Present it as a bearer token Every call carries an Authorization header. There is no second route in: not a query parameter, not a cookie, not a session.
  3. 3 Read the answer Every reply is a JSON object carrying an ok field. Where ok is false, an error field names the cause as a stable string meant for your code.

Presenting the key #

One key to an account. Any route other than the header below is unsupported by design — a key in a query string ends up in access logs, in browser history and in referrer headers, so it is refused outright rather than accepted and quietly leaked.

request
Authorization: Bearer sk_your_key_here

This key spends money Today it reads a balance; the same key will place orders once ordering exists. Handle it as a password: never in source control, never in a screenshot, replaced rather than passed around. Issuing a replacement retires the previous key on the spot.

Shapes and conventions #

Shapes and conventions
Base address https://smsactivate.io/api/v1
Transport Served over HTTPS only: a plain-HTTP call gets a redirect, and that redirect drops your Authorization header.
Format JSON both ways. What comes back is always an object — a bare array is never a valid response.
Success HTTP 200, and "ok" set to true.
Failure A 4xx or 5xx status, "ok" set to false, and an "error" string whose spelling never changes.
Money Amounts are US-dollar numbers — not strings, not cents: 0.84 is eighty-four cents.
Unknown fields Any response may gain fields over time. Skip the ones you do not recognise instead of treating them as an error.

When something fails #

Branch on the error string and nothing else. The sentence beside it exists for you rather than for your code, and may be reworded at any time; the string will not be.

When something fails
Status error Cause
401 missing_key No Authorization header arrived, or what arrived was not a bearer token.
401 bad_key The key is not one we know, or it has since been retired or replaced.
400 unknown_service No service answers to that code. Codes come out of the catalogue, never from the name shown on screen.
404 no_stock Nothing is idle for that service-and-country pairing at this moment.
404 unknown_country No country answers to that code for this product. The catalogues do not cover the same ground.
400 unknown_tech That network generation is not offered in this country. Only the United States is sold by metro.
429 rate_limited Too many calls. The Retry-After header names the wait, in seconds.

How hard you may push #

600 calls a minute, counted against the account rather than the address. A key is meant to live on one server — one address — so counting per address would penalise ordinary use while leaving a stolen key free to work from anywhere else. Past the ceiling you get a 429 with Retry-After in seconds.

Routes #

Account and catalogue reads. Each route names every parameter it accepts, every field it returns, and every way it can refuse.

What the account holds #

GET /api/v1/balance

The credit standing right now, and how many activations that covers at the lowest tariff currently in the catalogue.

What comes back

What the account holds — What comes back
Key Kind Meaning
ok boolean Present and true whenever the status is 200.
balance number The dollars on the balance, to the cent.
currency string Fixed at "USD"; it is sent so that nothing has to be assumed.
codes_at_cheapest integer The number of codes the balance would cover at the catalogue's lowest price. An estimate to give a sense of scale, not a quote; null when the catalogue holds nothing.
cheapest_code number That lowest price itself, in case you want to work the estimate out against a different figure.

Call

request
curl -s "https://smsactivate.io/api/v1/balance" \
  -H "Authorization: Bearer $SMSACTIVATE_KEY"

Answer

200 OK
{
  "ok": true,
  "balance": 42.5,
  "currency": "USD",
  "codes_at_cheapest": 425,
  "cheapest_code": 0.1
}

Ways it refuses missing_key bad_key rate_limited

One service in one country #

GET /api/v1/pricing?service={service}&country={country}

Live tariff and live idle count for a single pairing. Both drift: the tariff follows the cheapest carrier holding that service there, and the count is whatever the carrier reports at that instant.

What it takes

One service in one country — What it takes
Name Meaning
service needed The service's code, telegram for instance — lower-case, as spelt in the catalogue.
country may be omitted The country's code, england for instance. Drop it and every country comes back in one call — see below.

What comes back

One service in one country — What comes back
Key Kind Meaning
ok boolean Present and true whenever the status is 200.
service string The service code, returned unchanged.
country string The country code, returned unchanged.
price number The dollar price of one code on the cheapest network that carries it.
stock integer The count of numbers the networks report as free right now — a live reading that changes minute to minute.

Call

request
curl -s "https://smsactivate.io/api/v1/pricing?service=telegram&country=england" \
  -H "Authorization: Bearer $SMSACTIVATE_KEY"

Answer

200 OK
{
  "ok": true,
  "service": "telegram",
  "country": "england",
  "price": 0.84,
  "stock": 61213
}

Ways it refuses missing_key bad_key unknown_service no_stock rate_limited

One service, every country #

GET /api/v1/pricing?service={service}

Leave the country out and every country carrying the service comes back. The ordering is the site's own — what is actually delivering first, cheapest within that — rather than raw tariff, which would hoist a two-cent line nobody ever receives anything on to the top.

What it takes

One service, every country — What it takes
Name Meaning
service needed The service's code, telegram for instance.

What comes back

One service, every country — What comes back
Key Kind Meaning
ok boolean Present and true whenever the status is 200.
service string The service code you sent.
name string The name shown on the site, "Telegram" for instance.
countries array An object per country, ordered as explained above.
countries[].country string The country's code, ready to pass to the pair call.
countries[].name string Its English name.
countries[].price number The dollar price of one code.
countries[].stock integer Numbers free at this moment.

Call

request
curl -s "https://smsactivate.io/api/v1/pricing?service=telegram" \
  -H "Authorization: Bearer $SMSACTIVATE_KEY"

Answer

200 OK
{
  "ok": true,
  "service": "telegram",
  "name": "Telegram",
  "countries": [
    { "country": "england", "name": "United Kingdom", "price": 0.84, "stock": 61213 },
    { "country": "poland",  "name": "Poland",         "price": 0.91, "stock": 22140 }
  ]
}

Ways it refuses missing_key bad_key unknown_service rate_limited

Mobile session tariffs #

GET /api/v1/proxy-pricing?country={country}&tech={tech}

With no country named you get the four we keep modems in, each with a carrier count and an opening tariff. Name one and you get every carrier there with the cost of each stretch. Stretches are given in days: 1, 7, 30, 365.

What it takes

Mobile session tariffs — What it takes
Name Meaning
country may be omitted The country's code, us for instance. Leave it out and the countries are listed instead.
tech may be omitted The generation, 4g or 5g. Only the United States offers both; anywhere else the field is disregarded and the one available range is returned.

What comes back

Mobile session tariffs — What comes back
Key Kind Meaning
ok boolean Present and true whenever the status is 200.
country string The country code you sent.
tech string The generation the price applies to, after the default has been chosen.
techs array Each generation on sale in that country.
terms array The rental lengths on offer, in days.
carriers array An object per carrier.
carriers[].carrier string The carrier's code, to pass along when ordering.
carriers[].name string The carrier's display name, "T-Mobile" for instance.
carriers[].prices object The dollar price of each term, indexed by its length in days.

Call

request
curl -s "https://smsactivate.io/api/v1/proxy-pricing?country=us" \
  -H "Authorization: Bearer $SMSACTIVATE_KEY"

Answer

200 OK
{
  "ok": true,
  "country": "us",
  "tech": "4g",
  "techs": ["4g", "5g"],
  "terms": [1, 7, 30, 365],
  "carriers": [
    { "carrier": "us-t-mobile", "name": "T-Mobile", "prices": { "1": 8.67, "7": 52, "30": 130, "365": 1300 } },
    { "carrier": "us-verizon",  "name": "Verizon",  "prices": { "1": 8.67, "7": 52, "30": 130, "365": 1300 } }
  ]
}

Ways it refuses missing_key bad_key unknown_country unknown_tech rate_limited

Term tariffs #

GET /api/v1/rental-pricing?country={country}&days={days}

With no country named you get every country a line can be held in, each with its opening tariff. Name one and you get every operator there and what the stretch you asked about costs on it.

What it takes

Term tariffs — What it takes
Name Meaning
country may be omitted The country's code, fr for instance. Leave it out and the countries are listed instead.
days may be omitted The length in days — 7, 14 or 30. Any other value is treated as the shortest term.

What comes back

Term tariffs — What comes back
Key Kind Meaning
ok boolean Present and true whenever the status is 200.
country string The country code you sent.
days integer The term the price applies to.
terms array Each term on sale, in days.
operators array An object per operator, lowest price first.
operators[].operator string The operator's code, to pass along when ordering.
operators[].name string Its display name.
operators[].type string One of physical, virtual or premium.
operators[].price number The dollar price of the full term.

Call

request
curl -s "https://smsactivate.io/api/v1/rental-pricing?country=fr&days=30" \
  -H "Authorization: Bearer $SMSACTIVATE_KEY"

Answer

200 OK
{
  "ok": true,
  "country": "fr",
  "days": 30,
  "terms": [7, 14, 30],
  "operators": [
    { "operator": "fr-lycamobile", "name": "Lycamobile", "type": "virtual",  "price": 14.32 },
    { "operator": "fr-orange",     "name": "Orange",     "type": "physical", "price": 20.76 }
  ]
}

Ways it refuses missing_key bad_key unknown_country rate_limited

Worked examples #

Programs that run as they stand rather than one-line fragments — the two parts that trip people up included.

Shell #

Locate the cheapest country for a service, then confirm the balance stretches to it.

cheapest.sh
#!/usr/bin/env bash
set -euo pipefail
: "${SMSACTIVATE_KEY:?export your key first}"
API="https://smsactivate.io/api/v1"

auth=(-H "Authorization: Bearer $SMSACTIVATE_KEY")

# The catalogue is already ordered: the first country is the one to take.
best=$(curl -sf "${API}/pricing?service=telegram" "${auth[@]}" \
        | jq -r ".countries[0] | \"\(.country) \(.price)\"")
country=${best% *}
price=${best#* }

balance=$(curl -sf "${API}/balance" "${auth[@]}" | jq -r .balance)

# ⚠️ Compare as numbers, not as strings: "9.5" > "10" is true in a string sort.
if awk "BEGIN{exit !($balance >= $price)}"; then
  echo "ok: $country at \$$price, balance \$$balance"
else
  echo "top up first: need \$$price, have \$$balance" >&2
  exit 1
fi

JavaScript #

The same again in Node, handling the error shape properly.

pricing.mjs
const API = "https://smsactivate.io/api/v1";
const key = process.env.SMSACTIVATE_KEY;

async function call(path) {
  const r = await fetch(API + path, {
    headers: { Authorization: `Bearer ${key}` },
  });
  const body = await r.json();
  // A non-2xx always carries { ok:false, error }. Branch on `error`, never on
  // the sentence — the string is stable, the wording is not.
  if (!r.ok || !body.ok) {
    if (body.error === "rate_limited") {
      const wait = Number(r.headers.get("Retry-After") || 60);
      await new Promise((s) => setTimeout(s, wait * 1000));
      return call(path);
    }
    throw new Error(body.error ?? `http_${r.status}`);
  }
  return body;
}

const { countries } = await call("/pricing?service=telegram");
const { balance }   = await call("/balance");

const best = countries[0];
console.log(`${best.name}: $${best.price} (${best.stock} in stock)`);
console.log(balance >= best.price ? "balance covers it" : "top up first");

Python #

And in Python, retrying on 429 the same way.

pricing.py
import os, time, requests

API = "https://smsactivate.io/api/v1"
S = requests.Session()
S.headers["Authorization"] = f"Bearer {os.environ['SMSACTIVATE_KEY']}"

def call(path):
    r = S.get(API + path, timeout=20)
    body = r.json()
    if not r.ok or not body.get("ok"):
        if body.get("error") == "rate_limited":
            time.sleep(int(r.headers.get("Retry-After", 60)))
            return call(path)
        raise RuntimeError(body.get("error", f"http_{r.status_code}"))
    return body

countries = call("/pricing?service=telegram")["countries"]
balance   = call("/balance")["balance"]

best = countries[0]
print(f"{best['name']}: ${best['price']} ({best['stock']} in stock)")
print("balance covers it" if balance >= best["price"] else "top up first")

What is not here yet #

Today the key reads the account and the catalogue. Placing orders is next, and it will answer on the same base address to the same key — so nothing written against these routes will need unpicking when it lands.

Order a number Not yet Not yet. For now, ordering goes through the site.
Polling for the code Not yet Planned, alongside ordering.
Release a number Not yet Planned, alongside ordering.
Rental over API Not yet Planned.
Webhooks Not yet Not yet — it arrives together with order events, once there are any to deliver.

This page grows with them: what is here stays where it is, and anything new appears under Endpoints.

What changed #

  • First public release: API keys, GET /balance, GET /pricing.