Python SDK
pip install sendafrica — typed dataclass responses, async support via httpx, retry with backoff, and webhook verification for FastAPI/Django apps.
| Package | sendafrica on PyPI · v1.0.1 · stable |
| Install | pip install sendafrica |
| Requires | Python 3.9+ |
| Repo | github.com/SendAfrica/SendAfrica-python-sdk |
The Python SDK wraps the REST API in idiomatic Python — typed dataclass responses, automatic retry with exponential backoff on 429/5xx errors, local phone normalization to E.164 before any network call, and helpers for webhook signature verification.
#Installation
install.sh
bash
# Standard install
pip install sendafrica
# With async support (adds httpx)
pip install "sendafrica[async]"
# Development (testing, coverage, async)
pip install -e ".[dev,async]"#Client setup
auth.py
python
from sendafrica import SendAfrica
# Option 1: explicit key
client = SendAfrica(api_key="SA-xxxxx")
# Option 2: set env var, then no arguments needed
# export SENDAFRICA_API_KEY="SA-xxxxx"
client = SendAfrica()
# Full configuration
client = SendAfrica(
api_key="SA-xxxxx",
base_url="https://api.sendafrica.online/v1", # default
timeout=10, # seconds per request
max_retries=3, # retries on 429/5xx/connection errors
environment="production", # label for logging
debug=False, # True prints [sendafrica] logs to stdout
webhook_secret=None, # HMAC secret for webhook verification
)#Sending SMS
send.py
python
result = client.sms.send(
to="0712345678",
message="Your OTP is 123456",
sender="MyBrand", # optional, max 11 chars
)
print(result.message_id) # "SA-abc123..."
print(result.status) # "sent"
print(result.credits_used) # 1
# Preview cost without sending (zero network calls)
analysis = client.sms.analyze("Habari, how are you?")
print(analysis.encoding) # "GSM-7"
print(analysis.parts) # 1
print(analysis.credits) # 1#Bulk sending
bulk.py
python
results = client.sms.send_many([
{"to": "0711111111", "message": "Hello John"},
{"to": "0722222222", "message": "Hello Mary"},
{"to": "+255733333333", "message": "Hello Alex"},
], sender="MyBrand")
print(results.sent_count) # 2
print(results.failed_count) # 1
for failure in results.failed:
print(failure["index"], failure["to"], failure["error"])rate_limit_per_sec paces requests client-side (default 10/sec) so large batches never trip the API rate limit.
#Credits & payments
credits.py
python
balance = client.credits.balance()
print(balance.account_id) # "acc_abc123"
print(balance.balance) # 4820
transactions = client.credits.history(page=1, per_page=50)
for tx in transactions:
print(tx.id, tx.type, tx.amount, tx.balance_after, tx.created_at)
# Mobile money top-up
payment = client.payments.create(
amount=50000,
provider="snippe",
phone="0712345678",
)
print(payment.id, payment.status, payment.credit_amount)#Webhooks
webhooks.py
python
from fastapi import Request
@app.post("/webhooks/sendafrica")
async def webhook(request: Request):
event = client.webhooks.parse(
await request.body(),
signature=request.headers.get("X-SendAfrica-Signature"),
)
if event.type == "sms.delivered":
print(f"Message {event.message_id} delivered")
# event.type: "sms.delivered", "sms.failed", ...
# event.data: full raw event payload dict#Error handling
All errors inherit from SendAfricaError. Every exception carries .message, .status_code, .request_id, and .response_body; RateLimitError adds .retry_after:
errors.py
python
from sendafrica.exceptions import (
SendAfricaError,
InsufficientCreditsError,
RateLimitError,
InvalidPhoneError,
)
try:
client.sms.send(to="0712345678", message="Hello")
except InsufficientCreditsError:
print("Not enough credits -- top up first")
except RateLimitError as e:
print(f"Rate limited -- retry after {e.retry_after}s")
except InvalidPhoneError as e:
print(f"Bad phone number: {e.message}")
except SendAfricaError as e:
print(f"API error: {e} (status={e.status_code}, request_id={e.request_id})")| Exception | HTTP |
|---|---|
AuthenticationError | 401 |
ValidationError / InvalidPhoneError | 400 / 422 |
InsufficientCreditsError | 402 |
RateLimitError | 429 |
NotFoundError | 404 |
ServerError | 5xx |
APIConnectionError | network |
WebhookSignatureError | HMAC mismatch |