SendAfrica logoSendAfricaDocs

Rate Limits & Idempotency

Plan-based rate limits, X-RateLimit headers, and Idempotency-Key semantics that make retries safe.


#Rate limits

Rate limits apply per account based on plan, on both JWT and API-key routes alike:

PlanLimit
Free60 requests / minute
Pro600 requests / minute
Enterprise6,000 requests / minute

Public auth endpoints (register, login, etc.) are separately capped at 30 req/min per IP. Every response includes X-RateLimit-Limit and X-RateLimit-Remaining headers — surface them in your monitoring and back off before you hit the ceiling.

429 Too Many Requests
json
{
  "success": false,
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Too many requests"
  }
}

Official SDKs auto-retry on 429 and 5xx with exponential backoff — min(0.5 × 2^(attempt−1), 8.0) seconds, max 3 retries — and expose retry_after on RateLimitError so you can honor it manually too.

#Idempotency

Pass an Idempotency-Key header (any string, ≤128 chars) on POST /v1/sms/, POST /v1/sms/bulk, and campaign creation to make retries safe:

bash
bash
curl -X POST https://api.sendafrica.online/v1/sms/ \
  -H "X-API-Key: $SENDAFRICA_API_KEY" \
  -H "Idempotency-Key: order-1234-sms" \
  -H "Content-Type: application/json" \
  -d '{ "to": "0712345678", "message": "Your order shipped!" }'
ScenarioBehavior
First request succeedsResponse cached for 24 h — retries replay it instead of double-charging
Replayed responseIdentical body plus Idempotent-Replay: true response header
Original request failedNothing cached — a retry genuinely tries again
Same key while first request still in flight409 request_in_progress

Derive keys from business IDs

Use deterministic keys like order-{id}-confirmation rather than UUIDs — that way a retry after a timeout dedupes correctly even across processes.

#Credit ledger guarantees

Credits are charged per SMS part and tracked in an append-only ledger. Balance is derived from the latest completed transaction row — never stored in a separate column — and cached in Redis for 30 s. Every mutation goes through one atomic path: begin transaction → check idempotency → lock latest row FOR UPDATE → compute new balance (rejecting insufficient funds) → insert + commit. Concurrent sends can never overdraw an account.