SMSPING · GUIDES

Receive SMS verification codes with the SMSPing API in Python

This tutorial walks through one complete flow in under 80 lines of Python: look up a service and a country, choose an offer that is in stock, buy a one-time number, wait for the SMS, and confirm the order. Every endpoint and rule below comes from the SMS receiving API guide and the developer documentation. Check the docs for the current fields and limits before you go to production.

Buying a number deducts real balance from your SMSPing account. The script below only does a dry run unless you add --buy.

What you need

  • Python 3.8 or newer and the requests package (pip install requests).
  • An SMSPing account with balance, and an API key. Sign in and generate the key in Account Settings under API Management. The full key is shown once.
  • Keep the key on your server. Put it in an environment variable such as SMSPING_API_KEY, never in browser code, a URL or a public repository.

How the API is shaped

All endpoints live under https://smsping.net/api/customer.

Step Request Key needed
List services and countries GET /catalog No
Prices and stock for one pair GET /catalog/offers?country=<id>&service=<id> No
Buy a number POST /orders Yes
Read the order and the SMS GET /orders/:id Yes
Confirm after the SMS arrives POST /orders/:id/finish Yes
Cancel while still waiting POST /orders/:id/cancel Yes

A successful response looks like {"code": 0, "data": ...}. A failure comes back with a non-2xx status and a body with message and usually an errorCode such as INSUFFICIENT_BALANCE or PRICE_CHANGED.

Step 1: find the service and country IDs

GET /catalog is public. It returns data.services and data.countries, each with an id you pass to the other calls:

curl -s https://smsping.net/api/customer/catalog

For example, Telegram is telegram and the United States is usa. Look the IDs up rather than guessing them.

Step 2: pick an offer

GET /catalog/offers needs both country and service. Each offer has an operator, a price (in the currency field of the response, USD) and a stock. The script keeps offers with stock and picks the cheapest one. That rule is our choice for the example; you may prefer a specific operator.

Step 3: buy with an idempotency key

POST /orders takes country, service, operator, expectedPrice (the price you just saw) and an idempotencyKey of 16 to 100 letters, digits, _ or -.

  • Save the key before you send the request. If the request times out, retry the same purchase with the same key and the same body. A new key can start a second purchase.
  • If you get PRICE_CHANGED, fetch offers again and confirm the new price before buying.
  • HTTP 200 does not prove a number was assigned. Check data.status. If it is review, keep the order ID, query that order or contact support, and do not buy again.

Step 4: poll for the code

Call GET /orders/:id and wait at least 5 seconds between queries. When status becomes received, the code is in code (and the messages in sms). The other final statuses are finished, canceled, timeout, failed and review. The 10-minute stop in the script is a local safety limit we chose, not an API rule.

Step 5: finish or cancel

After the SMS arrives, call POST /orders/:id/finish. While you are still waiting you can call POST /orders/:id/cancel. A refund happens only after a confirmed cancellation or timeout with no SMS, so check the final status and the refunded field.

Rate limits

The current limit is 60 requests per minute per account. On HTTP 429, wait the number of seconds in the Retry-After header and send the same request again. The script falls back to 5 seconds if the header is missing; that fallback is our choice.

The full script

"""Receive an SMS verification code with the SMSPing API (Python 3.8+, `pip install requests`).

Set SMSPING_API_KEY in your environment. Nothing is bought unless you pass --buy.
"""
import os
import sys
import time
import uuid

import requests

API = "https://smsping.net/api/customer"


def call(method, path, *, params=None, body=None, auth=False):
    headers = {"Accept": "application/json"}
    if auth:
        headers["Authorization"] = "Bearer " + os.environ["SMSPING_API_KEY"]
    for _ in range(5):
        r = requests.request(method, API + path, params=params, json=body,
                             headers=headers, timeout=30)
        if r.status_code == 429:  # rate limited: wait Retry-After seconds, then resend the same request
            time.sleep(float(r.headers.get("Retry-After") or 5))
            continue
        payload = r.json()
        if not r.ok or payload.get("code") != 0:
            raise RuntimeError(f"HTTP {r.status_code} {payload.get('errorCode')}: {payload.get('message')}")
        return payload["data"]
    raise RuntimeError("still rate limited after 5 tries")


def cheapest_offer(country, service):
    data = call("GET", "/catalog/offers", params={"country": country, "service": service})
    in_stock = [o for o in data["offers"] if o["stock"] > 0]
    if not in_stock:
        sys.exit("No stock for this country/service right now.")
    return min(in_stock, key=lambda o: o["price"])


def wait_for_code(order_id, max_wait=600):
    deadline = time.time() + max_wait
    while time.time() < deadline:
        order = call("GET", f"/orders/{order_id}", auth=True)
        status = order["status"]
        if status == "received":
            return order
        if status in ("canceled", "timeout", "failed", "review", "finished"):
            sys.exit(f"Order ended with status={status}, refunded={order.get('refunded')}")
        time.sleep(5)  # the docs ask for at least 5 seconds between queries
    sys.exit("No SMS yet; cancel the order or check it again later.")


def main():
    country, service = "usa", "telegram"  # IDs come from GET /catalog
    offer = cheapest_offer(country, service)
    order_body = {
        "country": country,
        "service": service,
        "operator": offer["operator"],
        "expectedPrice": offer["price"],
        "idempotencyKey": uuid.uuid4().hex,  # keep it: reuse it if you retry this same purchase
    }
    print("Cheapest offer:", offer)
    if "--buy" not in sys.argv:
        print("Dry run. Would POST /orders with:", order_body)
        return

    order = call("POST", "/orders", body=order_body, auth=True)  # spends real balance
    if order["status"] not in ("pending", "waiting"):
        sys.exit(f"Not waiting for SMS: status={order['status']}")  # HTTP 200 alone is not success
    print("Number:", order["phone"])

    order = wait_for_code(order["id"])
    print("Code:", order["code"])
    call("POST", f"/orders/{order['id']}/finish", auth=True)


if __name__ == "__main__":
    main()

Run it:

pip install requests
export SMSPING_API_KEY="your-key"     # from Account Settings > API Management
python receive_sms.py                 # dry run: shows the offer and the order it would place
python receive_sms.py --buy           # buys a number (spends real balance) and waits for the code

Notes and limits

  • A key can access only the data of its own account. Resetting or disabling it, or changing the password or email, invalidates the old key.
  • Numbers are one-time activation numbers. Before buying, check that the target service accepts the number type you pick.

Help and FAQ

Do I need an API key to list prices?

No. /catalog and /catalog/offers are public. Orders, messages and balance need the Bearer key.

My purchase request timed out. Should I retry with a new key?

No. Retry with the same idempotencyKey and the same parameters, and check your existing orders. Use a new key only for a new, separate purchase.

How often can I check for the SMS?

At least 5 seconds apart, and within the 60 requests per minute account limit.

Ready to get started?

Sign in to the platform to generate an API key, and check the developer documentation for the current endpoints, fields and limits. Before purchasing, confirm that your chosen number type is accepted by the target service.

Open developer documentation