Tanzania · Airtel Money Open API

pyairtel

Python SDK for Airtel Money Tanzania — Collection, Disbursement & Refunds

Get Started View on GitHub
$ pip install pyairtel
PyPI Python CI License

What's included

Everything you need to
accept & send mobile money

🔐

OAuth2 Authentication

Automatic token acquisition and refresh. Your credentials stay safe — tokens are managed internally and renewed 60 seconds before expiry.

📲

USSD Push Collection

Request payments from subscribers. A prompt is sent to their phone — they enter their Airtel Money PIN to approve. No redirects needed.

Transaction Status Polling

Poll transaction status with typed responses: is_successful, is_pending, is_failed. No raw string parsing required.

💸

Disbursement (B2C)

Transfer money directly to any Airtel Money wallet. Includes payee validation before sending so you never send to an invalid number.

↩️

Refunds

Reverse a completed collection using Airtel's internal airtel_money_id. One line of code — no extra configuration needed.

🔒

RSA PIN Encryption

Disbursements require your merchant PIN encrypted with Airtel's RSA public key. Handled automatically via pycryptodome.

📞

Phone Normalisation

Pass numbers in any format — +255…, 0…, or 255…. Automatically normalised to the 12-digit format Airtel expects.

⚠️

ESB Error Decoding

All 9 real Airtel Tanzania ESB error codes mapped to human-readable messages. CollectionError.esb_code and .esb_message on every failure.

🧪

Sandbox & Production

Toggle between UAT sandbox and live production with a single sandbox=True/False flag. Same code, different environment.


Code examples

From zero to
first transaction

from pyairtel import AirtelMoney

# Initialise — sandbox=True for testing, False for production
airtel = AirtelMoney(
    client_id="your-client-id",
    client_secret="your-client-secret",
    sandbox=True,
)

# Send a USSD push — subscriber gets a PIN prompt on their phone
resp = airtel.collect(
    phone="+255681219610",   # +255, 0, or 255 formats all accepted
    amount=5000,              # Tanzanian Shillings
    reference="invoice-42",
)

print(resp.transaction_id)  # TXN-20240101120000-AB12CD34
print(resp.is_initiated)     # True
import time

# Wait for the subscriber to approve on their phone
time.sleep(15)

status = airtel.get_collection_status(resp.transaction_id)

if status.is_successful:
    print("✅ Payment confirmed!", status.airtel_money_id)

elif status.is_pending:
    print("⏳ Subscriber hasn't approved yet...")

elif status.is_failed:
    print("❌ Payment failed:", status.message)

# Transaction status codes:
# TS  — Transaction Successful
# TIP — Transaction In Progress
# TF  — Transaction Failed
# TA  — Transaction Ambiguous
# Use airtel_money_id from a successful collection status
status = airtel.get_collection_status(transaction_id)

if status.is_successful:
    refund = airtel.refund(status.airtel_money_id)

    if refund.is_successful:
        print("↩️  Refund initiated:", refund.message)
    else:
        print("Refund failed:", refund.message)
# Step 1 — check the payee can receive money
check = airtel.validate_payee("+255754123456")

if not check.is_valid:
    print("Cannot receive money:", check.message)
else:
    # Step 2 — transfer money (PIN is RSA-encrypted automatically)
    result = airtel.transfer(
        phone="+255681219610",
        amount=2000,
        pin="1234",                  # merchant PIN — encrypted before sending
        public_key_pem=open("airtel_pub.pem").read(),
        payer_first_name="Ronald",
        payer_last_name="Gosso",
        reference="payout-001",
    )

    print(result.is_successful, result.airtel_money_id)
from pyairtel import AirtelMoney, decode_esb_error
from pyairtel.exceptions import (
    AuthenticationError,
    CollectionError,
    DisbursementError,
    EncryptionError,
)

try:
    resp = airtel.collect(phone="+255754123456", amount=1000, reference="ref-1")

except AuthenticationError as e:
    print("Bad credentials:", e)

except CollectionError as e:
    print("ESB code:", e.esb_code)      # e.g. "ESB000014"
    print("Reason:",   e.esb_message)   # "Insufficient funds..."

# Decode any ESB code manually
print(decode_esb_error("ESB000039"))
# → "Transaction timed out. The subscriber did not respond..."

API Reference

AirtelMoney methods

Method HTTP Description Returns
collect(phone, amount, reference) POST Send USSD push payment request to subscriber CollectionResponse
get_collection_status(transaction_id) GET Poll status of a collection — TS / TIP / TF / TA TransactionStatusResponse
refund(airtel_money_id) POST Reverse a completed collection transaction RefundResponse
validate_payee(phone) GET Check if a number is a valid Airtel Money wallet ValidationResponse
transfer(phone, amount, pin, ...) POST Transfer money from merchant wallet to subscriber DisbursementResponse

Local Development

Run the package locally
step by step

01

Clone the repo

git clone https://github.com/ronaldgosso/pyairtel.git
cd pyairtel
02

Create & activate virtual environment

# Create venv
python -m venv venv

# Activate — Linux / Mac
source venv/bin/activate

# Activate — Windows
venv\Scripts\activate

# Confirm — should show venv path
which python
03

Install package + dev dependencies

pip install -e ".[dev]"

# For disbursement with RSA PIN encryption
pip install -e ".[dev,encryption]"
04

Fix lint errors automatically

# Auto-fix ruff issues (imports, whitespace, deprecated types)
ruff check pyairtel tests --fix --unsafe-fixes

# Format with black
black pyairtel tests
05

Verify full quality gate

# Linting — should print: All checks passed!
ruff check pyairtel tests

# Formatting — should print: X files would be left unchanged
black --check pyairtel tests

# Type checking — should print: Success: no issues found
mypy pyairtel
06

Run all tests

# Run all 25 tests with verbose output
pytest tests/ -v

# Run a specific test class
pytest tests/ -v -k "TestCollection"

# Run a single test
pytest tests/ -v -k "test_collect_success"
07

Deactivate venv when done

deactivate

Error Codes

Airtel Tanzania
ESB error reference

ESB000001
General ErrorTransaction failed — general error. Check credentials and try again.
ESB000004
Service UnavailableAirtel Money service is temporarily down.
ESB000008
Invalid TransactionThe request parameters are incorrect.
ESB000011
Subscriber Not FoundPhone number is not registered on Airtel Money.
ESB000014
Insufficient FundsSubscriber's Airtel Money balance is too low.
ESB000033
Transaction Limit ExceededAmount is above the subscriber's transaction limit.
ESB000036
Daily Limit ExceededSubscriber has reached their daily transaction limit.
ESB000039
Transaction Timed OutSubscriber did not respond to the USSD prompt in time.
ESB000041
PIN LockedToo many incorrect PIN attempts by the subscriber.
ESB000045
Duplicate TransactionA transaction with this ID was already processed.