Errors
Every failure returns the same JSON shape, with a stable machine-readable code.
json
{
"error": {
"code": "sender_id_not_approved",
"message": "Sender ID MYSCHOOL is pending approval.",
"field": "senderId",
"requestId": "REQ-90031",
"docs": "https://docs.selanim.co.tz/errors#sender_id_not_approved"
}
}Branch on error.code, never on the message text — messages are written for humans and may change. Always log requestId: it is the fastest way for support to find your call.
HTTP status codes
| Status | Meaning | When it happens |
|---|---|---|
| 200 | OK | The request succeeded. |
| 202 | Accepted | Messages were queued for dispatch. |
| 400 | Bad request | Malformed JSON or a failed validation rule. |
| 401 | Unauthorized | Missing, malformed, revoked or expired API key. |
| 402 | Payment required | The balance cannot cover the send. Nothing was sent. |
| 403 | Forbidden | Valid key, but not permitted — scope, IP allowlist or an unprovisioned account. |
| 404 | Not found | No resource with that id on your account. |
| 409 | Conflict | Duplicate idempotency key with a different body. |
| 422 | Unprocessable | Semantically invalid, e.g. an unapproved sender ID. |
| 429 | Too many requests | Rate limit exceeded — back off and retry. |
| 500 | Server error | Our fault. Safe to retry with the same idempotency key. |
| 503 | Service unavailable | A service behind the API is having a bad minute. Retry shortly. |
Error codes
| Code | HTTP | Cause | Fix |
|---|---|---|---|
invalid_api_key | 401 | The key is missing, malformed, or has never existed. | Check the Authorization header. Keys start with sbs_live_ or sbs_test_. |
api_key_revoked | 401 | The key was revoked, or replaced by a rotation. | Issue a new key in the portal and redeploy. |
api_key_expired | 401 | The key passed its expiry date. | Issue a new one; consider whether it needed an expiry at all. |
ip_not_allowed | 403 | Caller IP is outside the key's allowlist. | Add the address in the portal, or clear the allowlist. |
insufficient_scope | 403 | Valid key, but it does not hold the scope this endpoint needs. | Widen the key's scopes in the portal, or use a key that has them. |
invalid_request | 400 | Malformed JSON, a missing field, or a field this endpoint does not accept. | The message names the field. Unknown fields are refused rather than ignored, so a typo is reported instead of silently dropped. |
invalid_phone_number | 422 | A number is not a valid Tanzanian MSISDN. | Any of 0713111321, +255 713 111 321 or 255713111321 is accepted; anything else is not. |
recipients_exceeded | 400 | More than 1,000 recipients in one call. | Split the request, or import the list into a group. |
message_too_long | 422 | The body exceeds the 10-segment maximum. | Shorten it, or split it deliberately. |
sender_id_not_approved | 422 | The sender ID is pending, rejected, or not yours. | Send under an approved name while the request is reviewed. GET /v1/sender-ids lists them. |
not_found | 404 | No such resource on your account. | Check the id. An id belonging to another account also returns 404, on purpose. |
insufficient_funds | 402 | The balance cannot cover the whole send. | Top up, then retry with the same Idempotency-Key. Nothing was sent. |
account_not_provisioned | 403 | The key is not linked to a business account. | Finish onboarding in the portal; this normally means registration did not complete. |
idempotency_key_reused | 409 | That Idempotency-Key was used with a different request body. | Use a fresh key for a genuinely new request. |
idempotency_key_in_flight | 409 | The original call with this key is still running. | Wait for it to finish. Retrying now would risk a second send. |
rate_limited | 429 | Too many requests in the window. | Honour Retry-After. Sending fewer, larger requests is usually the real fix. |
upstream_unavailable | 503 | A service this request needed did not answer. | Retry with the same Idempotency-Key; it is safe and it is probably transient. |
internal_error | 500 | We broke. | Retry with the same Idempotency-Key, and quote the requestId if it persists. |
Retry safely
Pair every retry with the original
Idempotency-Key. A replayed key returns the first response instead of sending the message twice.
