Errors & Response Format
Every SendAfrica response uses one envelope. Learn the shape, pagination meta, and the full error-code reference.
#Response envelope
Every response — success or failure — follows the same envelope. All requests and responses use application/json unless noted otherwise (CSV import/export and provider webhooks are the exceptions).
success
json
{
"success": true,
"data": { "...": "..." },
"error": null,
"meta": null,
"request_id": "dfffa252-4781-43ff-8e1a-bf01a754d66a",
"timestamp": "2026-06-11T16:24:05Z"
}error
json
{
"success": false,
"data": null,
"error": {
"code": "invalid_credentials",
"message": "Invalid credentials"
},
"request_id": "abc123",
"timestamp": "2026-06-11T16:24:05Z"
}Always branch on success (or the HTTP status), then read error.code — codes are stable strings designed for programmatic handling.
#Pagination
Paginated endpoints return a meta object with everything needed for page controls:
json
json
{
"success": true,
"data": {
"items": ["..."],
"total": 150,
"page": 1,
"per_page": 25,
"total_pages": 6
}
}#Error codes
| HTTP | Code | Meaning |
|---|---|---|
| 400 | validation_error | Missing or invalid field |
| 400 | invalid_request | Malformed request body |
| 400 | weak_password | Password outside 12–128 characters (register) |
| 400 | invalid_otp | OTP expired or incorrect |
| 400 | invalid_phone | Not a valid Tanzania mobile number |
| 400 | phone_not_set | Phone OTP requested but no phone on the account |
| 400 | too_many_recipients | Bulk SMS exceeds 100 recipients |
| 400 | unknown_provider | Unrecognized OAuth provider name |
| 401 | invalid_credentials | Wrong email or password |
| 401 | unauthorized / invalid_token | Missing, malformed, or expired JWT |
| 401 | invalid_api_key / missing_api_key | Missing or invalid API key |
| 401 | invalid_refresh_token | Refresh expired, revoked, or already rotated |
| 401 | invalid_exchange_code | OAuth exchange code expired or already used |
| 401 | token_revoked | Token blacklisted (logged out elsewhere) |
| 402 | insufficient_credits | Not enough credits to send |
| 403 | email_not_verified | Email must be verified before this action |
| 403 | account_inactive | Account suspended or inactive |
| 403 | feature_disabled | Feature flag off (phone OTP, OAuth, mobile money) |
| 404 | not_found | Resource not found |
| 409 | email_exists | Email already registered |
| 409 | duplicate_contact | Phone already exists in that contact list |
| 409 | phone_not_verified | Mobile-money payment with unverified phone |
| 409 | request_in_progress | Same Idempotency-Key still being processed |
| 429 | rate_limit_exceeded | Too many requests |
| 500 | server_error | Internal server error |
#Handling errors in practice
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})")Include request_id in bug reports
Every response carries a request_id that correlates with server logs. Quote it when contacting support and it makes tracing instant.