Credits & Billing
Check your balance and read the append-only transaction ledger — purchases, deductions, refunds, and grants.
#Get balance
GET
/v1/credits/balanceJWT or API Keybalance.sh
bash
# Via API key
curl -s https://api.sendafrica.online/v1/credits/balance \
-H "X-API-Key: $SENDAFRICA_API_KEY"
# Via JWT
curl -s https://api.sendafrica.online/v1/credits/balance \
-H "Authorization: Bearer $JWT_TOKEN"200 OK
json
{
"success": true,
"data": {
"account_id": "486f8a6e-ea75-47ea-b176-c8e931aed058",
"balance": 5000
}
}#Transaction history
GET
/v1/credits/history?page=1&per_page=25JWT or API Key200 OK
json
{
"success": true,
"data": {
"items": [
{
"id": "e5f6a7b8-c9d0-...",
"type": "deduct",
"status": "completed",
"amount": -1,
"balance_after": 4999,
"description": "SMS to +255712345678",
"created_at": "2026-06-11T16:24:05Z"
},
{
"id": "f6a7b8c9-d0e1-...",
"type": "purchase",
"amount": 5000,
"balance_after": 5000,
"payment_order_id": "b2c3d4e5-...",
"description": "Package: Starter 5000 credits",
"created_at": "2026-06-10T10:00:00Z"
}
],
"total": 143,
"page": 1,
"per_page": 25,
"total_pages": 6
}
}#Reading the ledger
In practice only two type values are ever written — the description field tells apart *why* a purchase or deduction happened:
| Type | Meaning | Distinguished by description |
|---|---|---|
deduct | Credits removed | "Single API SMS send" or per-recipient charges during bulk/campaign sends |
purchase | Credits added | Confirmed payment order, admin grant ("Admin Grant: …"), or refund ("Refund for failed/rejected API SMS send") |
Don't branch on type alone
amount is signed (negative for deduct). balance_after gives a running ledger without recomputing client-side. To distinguish a refund from a top-up, read description — or check payment_order_id, which is non-null only for real purchases.
#Balance-check before batch pattern
preflight.py
python
from sendafrica.exceptions import InsufficientCreditsError
def safe_bulk_send(client, recipients, message):
analysis = client.sms.analyze(message)
needed = analysis.credits * len(recipients)
if client.credits.balance().balance < needed:
raise InsufficientCreditsError(
f"Need {needed} credits, top up first"
)
return client.sms.send_many(
[{"to": r, "message": message} for r in recipients]
)