Practical Python tutorial

Query SMS services, countries and prices with Python

Start an integration by reading the catalog before making any paid request. The example below uses only public GET endpoints: it lists services and countries, then optionally prints the lowest available quotes for one service. It requires no account API key, does not create orders and does not change a wallet balance.

What you need before running the example

Use Python 3.10 or newer with internet access. The program uses the standard library, so it does not require an SDK or a pip installation. Save the code as raysms_catalog.py. Keep the normal HTTPS certificate checks enabled; a connection failure should be investigated rather than bypassed.

Run python raysms_catalog.py to inspect a sample of the current service and country catalogs. The service identifier is the code field; a country uses its id field. They are provider identifiers, not display names or international dialing codes. Do not guess a country ID from a phone prefix.

Copy the complete read-only program

The code below is identical across this guide's languages. Its console labels and catalog names are English. BASE_URL points to this site's current canonical domain; it contains no credential. The request timeout and explicit error exits keep a failed catalog request from becoming an endless retry loop.

# Python 3.10+; standard library only. No API key is needed.
import argparse
import json
from decimal import Decimal
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request, urlopen

BASE_URL = "https://raysms.com"

def get_items(path, **params):
    query = urlencode({k: v for k, v in params.items() if v is not None})
    url = BASE_URL + "/api/" + path + ("?" + query if query else "")
    request = Request(url, headers={"Accept": "application/json",
                                    "User-Agent": "RaySMS-Catalog-Example/1.0"},
                      method="GET")
    try:
        with urlopen(request, timeout=15) as response:
            result = json.load(response)
    except HTTPError as error:
        if error.code == 429:
            delay = error.headers.get("Retry-After", "the documented interval")
            raise SystemExit("Rate limited; retry later after " + delay)
        raise SystemExit("API returned HTTP " + str(error.code)
                         + "; check the request and service availability")
    except (URLError, TimeoutError):
        raise SystemExit("Network request failed; check connectivity and retry later")
    except (ValueError, UnicodeError):
        raise SystemExit("API response was not valid JSON")
    if not isinstance(result, dict) or not isinstance(result.get("items"), list):
        raise SystemExit("Unexpected API response; inspect the API documentation")
    return result["items"]

def main():
    parser = argparse.ArgumentParser(description="Read the RaySMS catalog; never buy numbers")
    parser.add_argument("--service", help="Service code from the catalog, for example tg")
    parser.add_argument("--country", help="Optional country ID from the catalog")
    args = parser.parse_args()
    services = get_items("catalog/services", lang="en")
    countries = get_items("catalog/countries", lang="en")
    names = {str(country["id"]): country["name"] for country in countries}
    if not args.service:
        print("Services (first 20):")
        for service in services[:20]:
            print(service["code"], service["name"])
        print("Countries (first 10):")
        for country in countries[:10]:
            print(country["id"], country["name"])
        return
    if args.service not in {service["code"] for service in services}:
        parser.error("Unknown service code; run without arguments to inspect the catalog")
    if args.country is not None and args.country not in names:
        parser.error("Unknown country ID; inspect the country catalog")
    offers = get_items("catalog/prices", service=args.service, country=args.country)
    if not offers:
        print("No available offers for this selection. No purchase was made.")
        return
    for offer in sorted(offers, key=lambda row: int(row["price_micros"]))[:10]:
        price = Decimal(str(offer["price_micros"])) / Decimal("1000000")
        country = str(offer["country"])
        print(country, names.get(country, country),
              "USD " + format(price, ".6f"), "available:", offer["count"])

if __name__ == "__main__":
    main()

Choose a real service and interpret the quote

If tg appears in the current service catalog, python raysms_catalog.py --service tg queries its available countries. Add --country followed by an ID returned by the country catalog to narrow the query. These are syntax examples, not promises that a particular service or country will be in stock when you run them.

The API returns money in integer micro USD: 1,000,000 units equal one dollar. The program uses Decimal to print the price without binary floating-point rounding. count is the reported available quantity at that moment. An empty items list means this query found no available offer; it does not create a reservation.

Displayed stock and prices may change. A successful catalog response does not prove that a later purchase will succeed, that the target application will accept the number, or that a message will arrive.

Handle errors before automating repeated requests

HTTP 429 means requests must slow down; respect Retry-After when it is supplied. The sample exits so you can retry deliberately. A network error, timeout or HTTP 5xx should not be converted into an immediate busy loop. Check connectivity and service status, and use bounded backoff in a larger application.

Do not send cookies, passwords or API keys with these public catalog requests. This program is a starting point for reading data, not a production purchase worker. It deliberately contains no payment or order-creation call.

Move to purchasing only after an explicit decision

The separate developer guide explains authenticated orders, price ceilings, idempotency keys and status handling. Read it before adding any POST /api/orders request: that operation can spend wallet funds. A retry must refer to the same intended purchase, and an uncertain result must be investigated instead of silently buying again.

Open the developer workspace →