Skip to main content

pyairtel

pyairtel

PyPI version Python versions CI License: MIT

Python client for Airtel Money Tanzania — Collection & Disbursement APIs.

📖 Full documentation & examples → ronaldgosso.github.io/pyairtel


Features

  • 🔐 OAuth2 authentication with automatic token refresh
  • 📲 Collection — USSD push payments (request money from subscribers)
  • ✅ Transaction status polling
  • 💸 Disbursement — transfer money to Airtel Money wallets
  • ↩️ Refunds — reverse completed transactions by airtel_money_id
  • 🔒 RSA PIN encryption for disbursement security
  • 📞 Phone number normalisation (handles +255…, 0…, 255… formats)
  • ⚠️ ESB error decoding — all 9 Airtel Tanzania error codes mapped to human-readable messages
  • 🧪 Sandbox & Production environments

Installation

pip install pyairtel

For disbursement with PIN encryption, install the encryption extra:

pip install "pyairtel[encryption]"

Quick Start

1. Get credentials

Create an account at developers.airtel.co.tz, create an application, and add Collection and Disbursement APIs. Copy your client_id and client_secret from Key Management.

2. Collect money from a subscriber

from dotenv import load_dotenv
import os
from pyairtel import AirtelMoney

load_dotenv()

airtel = AirtelMoney(
    client_id=os.environ["AIRTEL_CLIENT_ID"],
    client_secret=os.environ["AIRTEL_CLIENT_SECRET"],
    sandbox=os.getenv("AIRTEL_SANDBOX", "true").lower() == "true",       # set False for production
)

response = airtel.collect(
    phone="+255681219610",
    amount=5000,
    reference="invoice-42",
)

print(response.transaction_id)  # TXN-20240101120000123456-AB12CD34
print(response.is_initiated)    # True

3. Check transaction status

import time

time.sleep(15)  # give the subscriber time to approve

status = airtel.get_collection_status(response.transaction_id)

if status.is_successful:
    print("Payment confirmed!", status.airtel_money_id)
elif status.is_pending:
    print("Still waiting for subscriber to approve...")
elif status.is_failed:
    print("Payment failed:", status.message)

4. Transfer money to a subscriber (Disbursement)

# First validate the payee
check = airtel.validate_payee("+255681219610")
if not check.is_valid:
    print("Payee cannot receive money:", check.message)
else:
    result = airtel.transfer(
        phone="+255681219610",
        amount=2000,
        pin="1234",                              # your merchant PIN
        public_key_pem=open("airtel_pub.pem").read(),  # from Key Management
        payer_first_name="Ronald",
        payer_last_name="Gosso",
        reference="payout-001",
    )
    print(result.is_successful, result.airtel_money_id)

5. Refund a transaction

# 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)
    print(refund.is_successful, refund.message)

Environment

Parameter Sandbox Production
sandbox True False
Base URL https://openapiuat.airtel.africa https://openapi.airtel.africa

Credentials & Environment Variables

Never hardcode credentials in your code. Copy .env.example to .env and fill in your values:

cp .env.example .env

.env:

AIRTEL_CLIENT_ID=your-client-id-here
AIRTEL_CLIENT_SECRET=your-client-secret-here
AIRTEL_SANDBOX=true
AIRTEL_PUBLIC_KEY_PATH=airtel_pub.pem   # disbursement only

Then load it in your project:

from dotenv import load_dotenv 
import os
from pyairtel import AirtelMoney

load_dotenv()

airtel = AirtelMoney(
    client_id=os.environ["AIRTEL_CLIENT_ID"],
    client_secret=os.environ["AIRTEL_CLIENT_SECRET"],
    sandbox=os.getenv("AIRTEL_SANDBOX", "true").lower() == "true",
)

⚠️ .env and *.pem are in .gitignore — never commit them.

Error Handling

from pyairtel import AirtelMoney, decode_esb_error
from pyairtel.exceptions import AuthenticationError, CollectionError, EncryptionError

try:
    resp = airtel.collect(phone="+255681219610", amount=1000, reference="ref-1")
except AuthenticationError as e:
    print("Bad credentials or token expired:", e)
except CollectionError as e:
    print("Collection failed:", 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 to the USSD prompt in time."
Exception When raised
AuthenticationError Token acquisition fails (bad credentials, network)
CollectionError USSD push, status check, or refund fails — includes .esb_code and .esb_message
DisbursementError Transfer or payee validation fails
EncryptionError RSA PIN encryption fails (e.g. missing pycryptodome)
ValidationError Invalid phone number format

Airtel Tanzania ESB Error Codes

Code Meaning
ESB000001 General error — check credentials and try again
ESB000004 Service unavailable — Airtel Money is temporarily down
ESB000008 Invalid transaction — request parameters are incorrect
ESB000011 Subscriber not found — number not registered on Airtel Money
ESB000014 Insufficient funds — subscriber balance too low
ESB000033 Transaction limit exceeded — amount above subscriber's limit
ESB000036 Daily limit exceeded — subscriber has hit their daily cap
ESB000039 Transaction timed out — subscriber didn't respond to USSD prompt
ESB000041 PIN locked — too many incorrect PIN attempts
ESB000045 Duplicate transaction — this ID was already processed

Local Development

Follow these steps to run pyairtel locally, run tests, and contribute.

1. Clone the repository

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

2. Create and activate a virtual environment

# Create venv
python -m venv venv

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

# Activate — Windows
venv\Scripts\activate

# Confirm — should show the venv path
which python

3. Install the package and dev dependencies

pip install -e ".[dev]"

# Also install encryption support for disbursement tests
pip install -e ".[dev,encryption]"

# install from requirements.txt
pip install -r requirements.txt

4. Fix lint errors automatically

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

# Format with black
black pyairtel tests

5. Verify the 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 in 7 source files
mypy pyairtel

6. Run the test suite

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

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

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

7. Deactivate the venv when done

deactivate

Contributing

Contributions are welcome! Please follow these steps:

  1. Fork the repository on GitHub
  2. Create a feature branch: git checkout -b feat/your-feature-name
  3. Make your changes — all new code must include tests
  4. Run the full quality gate (steps 4–6 above) and ensure everything passes
  5. Commit with a descriptive message: git commit -m "feat: add your feature"
  6. Push and open a Pull Request against main

Please keep PRs focused — one feature or fix per PR. If you're unsure whether something is in scope, open an issue first.


License

MIT © Ronald Isack Gosso

Metadata

Release files for pyairtel 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyairtel 0.1.1
File Size Uploaded
pyairtel-0.1.1.tar.gz 264.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pyairtel 0.1.1
File Interpreter ABI Platform
pyairtel-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 280.8 kB

Release files / pyairtel-0.1.1.tar.gz

Download URL pyairtel-0.1.1.tar.gz
Size 264.6 kB
Tags Source
SHA-256 checksum
How to use checksums
343e667c9c993db72f993a3b614875d7796b0361d88f938b3d91760f150929fa
BLAKE2b-256 checksum
How to use checksums
1b543d2cd9c9479f7219b88d4ee5dc16583ed113c3df51083d2c551238b617ec
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 31, 2026.

Transparency log

Release files / pyairtel-0.1.1-py3-none-any.whl

Download URL pyairtel-0.1.1-py3-none-any.whl
Size 16.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f8aff57c67d24f3447fce475e7baca36fa578e1d3c52aee6ce0d7771d3d7a4d2
BLAKE2b-256 checksum
How to use checksums
cdab414caf98e1181f085d56df37b69254eea985f450638e1a08514455ba6cb0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 31, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page