SendAfrica logoSendAfricaDocs

Integrate with AI Assistants

A single page written for coding agents (Cursor, Claude, ChatGPT, Copilot): everything needed to ship a working SendAfrica SMS integration — env setup, complete Python & TypeScript recipes, webhook verification, and error handling.


This page is designed to be pasted into or fetched by an AI coding assistant. It contains everything an agent needs to produce a complete, production-safe integration where the developer only supplies their API key. Deeper reference lives in Send SMS, Errors, Rate Limits & Idempotency, and Webhooks.

#Facts the agent must know

  • Base URL: https://api.sendafrica.online — all endpoints under /v1.
  • Server-to-server auth: header X-API-Key: SA-.... The key is shown once at creation; store it in an environment variable, never in code.
  • Every response uses the envelope { success, data, error, request_id, timestamp }. Log request_id for support.
  • Phone numbers: Tanzanian formats 0712345678 and +255712345678 are both accepted and normalized to E.164 automatically. Non-TZ numbers are rejected.
  • Sending is synchronous: a successful response means the message is submitted and has status: "sent". Final delivery arrives later via webhook (delivered / failed).
  • Billing is per SMS part in credits (160 chars GSM-7, 70 chars UCS-2 — emoji force UCS-2). Insufficient credits fail with 402 insufficient_credits.
  • Sender ID: omit it and messages send from the platform default SendAfrika. Custom sender IDs must be registered first — unregistered ones are silently swapped for the fallback sender. See Sender IDs.
  • Retries are safe when you pass an Idempotency-Key header (or the SDK's idempotencyKey option); replays return the cached response for 24 h.
  • Machine-readable contract: fetch openapi.json (OpenAPI 3.1) to generate typed clients or validate requests.

#Environment setup

.env
bash
SENDAFRICA_API_KEY=SA-your-key-here
SENDAFRICA_WEBHOOK_SECRET=whsec_your-webhook-secret

Never commit real keys. Load them from the environment; if a key leaks, revoke it from the dashboard immediately.

#Python — complete recipe

install.sh
bash
pip install sendafrica
send_sms.py
python
import os
from sendafrica import SendAfrica
from sendafrica.exceptions import (
    AuthenticationError,
    InsufficientCreditsError,
    InvalidPhoneError,
    RateLimitError,
    ServerError,
)

# Reads SENDAFRICA_API_KEY from the environment automatically
client = SendAfrica()

result = client.sms.send(
    to="0712345678",                 # normalized to +255712345678
    message="Your OTP is 482910. It expires in 5 minutes.",
    # sender="MYBRAND",              # optional — must be registered
)

print(result.message_id)     # "SA-..."
print(result.status)         # "sent"
print(result.credits_used)   # 1

balance = client.credits.balance()
print(balance.balance)       # remaining credits

The SDK retries 429/5xx automatically (exponential backoff, max 3). Handle these exceptions explicitly:

errors.py
python
from sendafrica.exceptions import (
    AuthenticationError,
    InsufficientCreditsError,
    InvalidPhoneError,
    RateLimitError,
    SendAfricaError,
)

try:
    client.sms.send(to="0712345678", message="Hello")
except InvalidPhoneError:
    ...  # reject the input — not a transient error
except InsufficientCreditsError:
    ...  # top up before retrying
except RateLimitError as e:
    ...  # honor e.retry_after seconds
except AuthenticationError:
    ...  # key revoked or wrong
except SendAfricaError as e:
    print(e.status_code, e.request_id)  # include request_id in support tickets

#TypeScript / Node — complete recipe

install.sh
bash
npm install sendafrica
send.ts
typescript
import { SendAfricaClient } from "sendafrica";
import { normalizeTzPhone, getSmsPartInfo } from "sendafrica";

const client = new SendAfricaClient({
  apiKey: process.env.SENDAFRICA_API_KEY!, // required
  timeoutMs: 15_000,
});

const result = await client.sendSms(
  {
    to: normalizeTzPhone("0712345678"),   // "+255712345678"
    message: "Your OTP is 482910. It expires in 5 minutes.",
    // from: "MYBRAND",                   // optional — must be registered
  },
  { idempotencyKey: "otp-482910" },       // makes retries safe
);

console.log(result.messageId);   // "SA-..."
console.log(result.status);      // "sent"
console.log(result.creditsUsed); // 1

const balance = await client.getBalance();
console.log(balance.balance);

// Preview parts/credits without sending (zero network calls)
console.log(getSmsPartInfo("Hello 😊")); // { encoding: "UCS-2", parts: 1, ... }

#Other official SDKs

If the target stack isn't Python or TypeScript, prefer one of these — they share the same resource model, env-var key resolution (SENDAFRICA_API_KEY), and retry behavior:

SDKInstallRequires
C# (.NET)dotnet add package SendAfrica.NET 8 / Std 2.0
PHPcomposer require sendafrica/sendafricaPHP 7.4+
C++conan install sendafrica/1.0.1C++17, CMake 3.16+
Dartpub (coming soon)Dart 3+

#Any other language — use the REST API directly

For stacks without an SDK (Go, Java, Ruby, Rust, Elixir, …), call the plain HTTP API — it's a single JSON POST. Generate a typed client from openapi.json with any OpenAPI generator, or use this reference request:

send.sh
bash
curl -s -X POST https://api.sendafrica.online/v1/sms/ \
  -H "X-API-Key: $SENDAFRICA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: otp-482910" \
  -d '{
    "to": "0712345678",
    "message": "Your OTP is 482910. It expires in 5 minutes."
  }'
200 OK
json
{
  "success": true,
  "data": {
    "message_id": "SA-f3b1c2d49e8a4f2bb1c2d3e4f5a6b7c8",
    "status": "sent",
    "cost": "TZS 35.00",
    "credits_used": 1
  },
  "request_id": "dfffa252-4781-43ff-8e1a-bf01a754d66a",
  "timestamp": "2026-06-11T16:24:05Z"
}
  • Check success first; on failure read error.code and map it per the table below.
  • Retry only with the same Idempotency-Key; honor Retry-After on 429.
  • Bulk sends: POST /v1/sms/bulk with { "to": [...], "message": "..." } (max 100 numbers).
  • Balance: GET /v1/credits/balance. Message logs: GET /v1/sms/logs?status=delivered.

#Receiving delivery webhooks

Configure your webhook URL in the dashboard, then verify the X-SendAfrica-Signature header (HMAC-SHA256 of the raw body with your webhook secret) before trusting any payload. Events include sms.delivered and sms.failed; deduplicate by event/message ID because replays can occur.

webhook.py
python
from sendafrica import SendAfrica

client = SendAfrica()  # reads SENDAFRICA_WEBHOOK_SECRET too

@app.post("/webhooks/sendafrica")
async def webhook(request):
    event = client.webhooks.parse(
        await request.body(),
        signature=request.headers.get("X-SendAfrica-Signature"),
    )  # raises WebhookSignatureError on mismatch

    if event.type == "sms.delivered":
        await mark_delivered(event.message_id)
    elif event.type == "sms.failed":
        await alert_ops(event.message_id, event.data)

    return {"ok": True}

#HTTP error codes worth handling

StatusCodeAgent action
401invalid_api_key / token_expiredCheck key/env var; never retry as-is
402insufficient_creditsSurface a top-up prompt; don't retry
403invalid_phone_numberReject the input; don't retry
429rate_limit_exceededWait Retry-After seconds, then retry once
500–503gateway_error etc.Retry with backoff + same Idempotency-Key

Checklist for the agent

Load the key from the environment → normalize/validate TZ numbers → send with an idempotency key → treat sent as submitted, not delivered → verify webhook signatures before parsing → handle the five error rows above → log request_id on failures.