SMS Verification API for Python
This is the Python integration guide for the SMSBulk SMS verification API: your script buys a dedicated phone number, waits for the verification SMS and reads the code as JSON. One pip install (requests) covers the whole flow, and the production-ready script below is copy-paste runnable.
Install with pip
The only dependency is requests; there is no required SDK because the API is plain REST and JSON. Create a key in the dashboard, export it as an environment variable and run the script. If your project already uses httpx or aiohttp, every call translates one to one.
python3 --version # 3.9 or newer
# One dependency, in a virtual environment:
python3 -m venv .venv && source .venv/bin/activate
pip install requests
# Keep the API key out of your source code:
export SMSBULK_API_KEY=smsbulk_your_key_here
python verify.pyAuthentication in one header
Every authenticated request sends the key as an X-API-Key header, which a requests.Session sets once for the whole flow. Keep the key in an environment variable or your secret manager, never in source control. Keys are created, rotated and revoked in the dashboard, and the API documentation lists every endpoint the key unlocks.
Production-ready OTP flow in Python
The loop is the same three calls the platform is built around: create an activation, poll until the code arrives, confirm it. This version is hardened the way a real service needs: a session with a conservative urllib3 Retry policy, a timeout on every request, and a cancel path so a wait that times out is refunded automatically.
# verify.py (pip install requests)
import os
import time
import requests
from requests.adapters import HTTPAdapter, Retry
BASE = "https://smsbulk.net/api/v1"
# Retries are deliberately conservative: rate limits and 5xx are retried
# with backoff for GET and DELETE only. A failed POST is never retried
# blindly, so an ambiguous error can never buy two numbers.
session = requests.Session()
session.headers["X-API-Key"] = os.environ["SMSBULK_API_KEY"] # smsbulk_...
session.mount(
"https://",
HTTPAdapter(
max_retries=Retry(
total=3,
backoff_factor=1,
status_forcelist=(429, 500, 502, 503, 504),
allowed_methods=("GET", "DELETE"),
)
),
)
class SmsBulkError(RuntimeError):
pass
def api(method: str, path: str, timeout: float = 15, **kwargs):
r = session.request(method, BASE + path, timeout=timeout, **kwargs)
if r.status_code >= 400:
raise SmsBulkError(f"SMSBulk API {r.status_code}: {r.text}")
return r.json() if r.text else None
def receive_otp(
service_code: str,
country_iso: str,
poll_s: float = 5,
max_wait_s: float = 600,
) -> str:
# 1) Buy a dedicated number
activation = api(
"POST",
"/activations",
json={"serviceCode": service_code, "countryIso": country_iso},
)
print("Number:", activation["phoneNumber"])
# 2) Poll until the verification code arrives
deadline = time.monotonic() + max_wait_s
while time.monotonic() < deadline:
time.sleep(poll_s)
a = api("GET", f"/activations/{activation['id']}")
if a["status"] == "RECEIVED":
# 3) Confirm the code was used
api("POST", f"/activations/{activation['id']}/complete")
return a["smsCode"]
if a["status"] in ("CANCELLED", "EXPIRED", "REFUNDED"):
raise SmsBulkError("Ended without SMS: " + a["status"])
# 4) Give up: cancel so the charge returns to your balance
# automatically. Cancellation unlocks a couple of minutes after
# purchase, so keep max_wait_s comfortably above that window.
api("DELETE", f"/activations/{activation['id']}")
raise TimeoutError(f"No SMS within {max_wait_s:.0f}s, activation cancelled and refunded")
if __name__ == "__main__":
print("Verification code:", receive_otp("tg", "US"))The request signatures are exactly the ones verified end to end against the production API. Two details worth keeping: the Retry policy deliberately excludes POST (an ambiguous error must not buy two numbers), and cancellation unlocks a couple of minutes after purchase, so keep the wait window above that. Every endpoint and response field is specified in the API documentation.
Polling, not webhooks
The v1 API is polling based: you ask for the activation state until the status flips to RECEIVED and the smsCode field is filled. There is no webhook to configure, which also means no public callback URL to expose and secure. A five second sleep is a good default; most codes arrive within a few polls.
In a Django or Flask application, run the polling loop in a background worker (a Celery task fits naturally) rather than inside the request cycle, and store the activation id so a restarted worker can resume the same activation instead of buying a new number.
Receiving SMS in Python without scraping
A lot of "python receive sms" tutorials end up scraping public receive-SMS websites. That approach breaks constantly (markup changes, rate limiting, captchas) and the numbers are shared inboxes: anyone can read the code meant for you, and most platforms have already blacklisted those ranges.
An API gives you the same outcome properly: a dedicated number allocated to you alone, a JSON field with the code, and an automatic refund when nothing arrives. No HTML parsing, no shared inbox, no guessing which number still works. If your use case is verifying your own signup flow with real numbers in CI, the SMS verification testing guide covers that end to end.
Live starting prices for popular services
Prices are per successful verification and vary by country. These starting prices come from the live catalogue and refresh hourly on this page:
Your code can read the same data: the catalogue endpoints are public, and the service catalogue page lists everything you can verify.
Python SMS API FAQ
Should I use requests or httpx?
requests with an HTTPAdapter Retry policy, as in the script above, is the battle-tested synchronous choice. Pick httpx when you need async or HTTP/2; the endpoints, headers and JSON bodies are identical, so switching later is mechanical.
Is there an official pip package or SDK?
No SDK is required: the API is plain REST and JSON, and requests is the only dependency in the flow above. If you are building an AI agent rather than a classic service, the official MCP server documentation exposes the same operations as native tools.
How do I run this with asyncio?
Swap requests for httpx.AsyncClient and time.sleep for asyncio.sleep; the polling loop maps one to one. Alternatively keep the synchronous flow and run it in a thread or process pool if async is only needed at the edges of your application.
How does this fit Django, Flask or Celery?
Treat a verification as a background job: enqueue a task with the service code and country, let the worker create the activation and poll, and persist the activation id so retries resume instead of re-buying. Keep the polling out of the web request cycle.
Can I run many verifications in parallel?
Yes, activations are independent, so a thread pool or an asyncio gather over the flow works. Respect the documented rate limits when you scale up, list activations with cursor pagination, and reconcile costs per activation from the wallet transaction log.
What happens if the code never arrives?
The flow above cancels the activation after the wait window and the charge returns to your balance automatically. You only pay for verifications that deliver a code.
Keep exploring
The SMS verification API page covers the language-neutral API in depth, and the same flow in Node.js lives at SMS API for Node.js. Everything else, from error codes to pagination, is in the API documentation.
