Powering SA prepaid since 2003 / No monthly fees, ever
SIMcloud bulk airtime and data recharge platform

SIMcloud API

How to Send Airtime and Data with the SIMcloud API in Python

A Python recharge integration using the live data catalogue, five-minute duplicate handling and background status polling.

Author: SIMcloud Published 15 July 2026
Python service distributing airtime and data through a secure recharge API

A Python service can use the SIMcloud API to recharge South African airtime and data, then poll the original request until it is delivered or failed. Airtime accepts a rand amount; data should be selected from the live product catalogue for the recipient’s network.

The examples use the popular requests package and explicit handling for authentication, validation and the five-minute duplicate rule.

Set up the Python client

python -m pip install requests

Store the token in SIMCLOUD_API_TOKEN. Do not commit it to source control.

import os
import requests

BASE_URL = "https://simcloud.co.za"
TOKEN = os.environ.get("SIMCLOUD_API_TOKEN")

if not TOKEN:
    raise RuntimeError("SIMCLOUD_API_TOKEN is not configured")

SESSION = requests.Session()
SESSION.headers.update({
    "Authorization": f"Bearer {TOKEN}",
    "Accept": "application/json",
})

def simcloud_request(method, path, payload=None):
    response = SESSION.request(
        method,
        f"{BASE_URL}{path}",
        json=payload,
        timeout=(10, 30),
    )
    body = response.json()
    return response.status_code, body

Queue an airtime order

The current Airtime API accepts msisdn, network, amount and reference. Supported friendly network values include MTN, Vodacom, Cell C and Telkom variants documented by SIMcloud. The amount must currently be between R2 and R999.

def create_airtime_order(msisdn, network, amount, reference):
    status, body = simcloud_request("POST", "/api/airtime.php", {
        "msisdn": msisdn,
        "network": network,
        "amount": amount,
        "reference": reference,
    })

    if status == 201:
        return body
    if status == 409:
        raise RuntimeError("Duplicate airtime order inside five-minute window")
    if status == 401:
        raise RuntimeError("SIMcloud authentication failed")

    raise RuntimeError(body.get("message", "Airtime order failed"))

Store the returned request_id immediately. It identifies the queued order for later polling.

Fetch the data catalogue before ordering

Data bundles are network-specific products. Use the live catalogue rather than accepting an arbitrary rand amount from the user.

from urllib.parse import urlencode

def list_data_products(network=None):
    query = {"products": 1}
    if network:
        query["network"] = network

    status, body = simcloud_request(
        "GET",
        f"/api/data.php?{urlencode(query)}",
    )

    if status != 200:
        raise RuntimeError(body.get("message", "Could not load data products"))

    return body.get("products", [])

The response includes product ID, internal network, network name, description, group name, amount and sell value. Present a current product choice to the user and keep the returned network and value together.

Queue the selected data bundle

def create_data_order(msisdn, product, reference):
    status, body = simcloud_request("POST", "/api/data.php", {
        "msisdn": msisdn,
        "network": product["network"],
        "amount": product["sellvalue"],
        "reference": reference,
    })

    if status == 201:
        return body
    if status == 409:
        raise RuntimeError("Duplicate data order inside five-minute window")
    if status == 401:
        raise RuntimeError("SIMcloud authentication failed")

    raise RuntimeError(body.get("message", "Data order failed"))

Do not retry HTTP 409 as if it were a temporary error. It means the same MSISDN and amount was ordered recently. Load and reconcile the original local request.

Poll airtime or data status

def get_recharge_status(service, request_id):
    if service not in {"airtime", "data"}:
        raise ValueError("Unsupported recharge service")

    status, body = simcloud_request(
        "GET",
        f"/api/{service}.php?request_id={int(request_id)}",
    )

    if status == 404:
        return {"status": "not_found"}
    if status != 200:
        raise RuntimeError(body.get("message", "Status lookup failed"))

    return body

Queued and pending are non-final. Delivered and failed are final. Use a background queue with bounded polling intervals instead of sleeping inside a web request.

Store a local order model

For each request retain:

  • Local immutable ID and unique reference.
  • Service, MSISDN, network and requested value.
  • Selected data product ID or description where relevant.
  • SIMcloud request ID and platform order number when returned.
  • HTTP status and queue/final status.
  • Created, last-polled and completed timestamps.
  • Safe failure reason without bearer token or unrelated personal data.

Use a reliable worker workflow

  1. Validate recipient, network and product locally.
  2. Create the local pending order.
  3. Call SIMcloud once.
  4. Persist the returned request ID.
  5. Queue a poll job.
  6. Update the same record until delivered or failed.
  7. Reconcile billing and inform the calling application.

If the POST times out after leaving your server, treat the result as unknown. Search your local logs and reconciliation path before sending the same recharge again.

Frequently asked questions

Can Python send airtime and data through the same endpoint?

No. Use /api/airtime.php for airtime and /api/data.php for data.

How do I choose a data bundle?

Fetch the live data catalogue, optionally filtered by network, and order using the returned network and sell value.

What does HTTP 409 mean?

The same MSISDN and amount was ordered inside the documented five-minute duplicate window.

When is the recharge final?

Delivered and failed are final. Queued and pending require later polling.

Should the token be stored in the Python source?

No. Load it from secure runtime configuration and keep it out of logs and repositories.

Put this guide into practice

Build reliable South African recharge workflows in Python

Select valid products, persist request IDs and reconcile every delivered or failed order.