Skip to main content

SMS Error Codes

A complete reference of the errors returned by the SMS API, including HTTP request errors and per-message delivery failure codes. Use these codes to build robust retry logic and clear user-facing messaging.

๐Ÿงฑ Error Response Formatโ€‹

All API errors share a consistent shape:

{
"error": {
"code": "INVALID_PHONE",
"message": "The 'to' field is not a valid E.164 number",
"details": { "field": "to" },
"timestamp": "2026-05-28T10:30:00Z"
}
}
FieldDescription
codeStable machine-readable error code
messageHuman-readable explanation
detailsOptional contextual data (e.g. offending field)
timestampWhen the error occurred (ISO 8601)

๐ŸŒ HTTP Status Codesโ€‹

HTTPMeaning
200Success
400Bad request โ€” invalid or missing parameters
401Unauthorized โ€” missing or invalid API key
402Payment required โ€” insufficient balance
404Not found โ€” unknown message_id
422Unprocessable โ€” request valid but cannot be fulfilled
429Too many requests โ€” rate limit exceeded
500Internal server error

๐Ÿšซ Request Error Codesโ€‹

Returned synchronously when a POST/GET request cannot be accepted.

CodeHTTPDescriptionResolution
INVALID_PHONE400to is not valid E.164Include the country code, e.g. +251911234567
INVALID_SENDER_ID400from is malformedUse 3-11 alphanumeric characters
SENDER_ID_NOT_APPROVED422Sender ID not yet approvedRegister and await approval โ€” see Sender IDs
MESSAGE_TOO_LONG400text exceeds 1600 charsShorten the message
MISSING_PARAMETER400A required field is absentCheck details.field
BATCH_TOO_LARGE400More than 1,000 messagesSplit into smaller batches
UNAUTHORIZED401Bad or missing API keyVerify your Authorization header
INSUFFICIENT_BALANCE402Account balance too lowTop up your account
MESSAGE_NOT_FOUND404Unknown message_idConfirm the ID returned at send time
RATE_LIMITED429Exceeded 100 req/secBack off and retry
INTERNAL_ERROR500Unexpected server errorRetry; contact support if it persists

๐Ÿ“ญ Delivery Failure Codesโ€‹

Returned asynchronously via Delivery Reports as the error_code when a message reaches failed status.

CodeDescriptionResolution
DLR_INVALID_NUMBERNumber does not exist or is unreachableValidate the recipient
DLR_ABSENT_SUBSCRIBERHandset off or out of coverageRetry later
DLR_REJECTED_OPERATOROperator blocked the messageCheck sender ID / content compliance
DLR_BLOCKED_CONTENTMessage flagged as spamReview content and templates
DLR_DNDRecipient on a do-not-disturb listRespect opt-outs
DLR_EXPIREDValidity period elapsed before deliveryResend
DLR_PORTED_NUMBERRouting failed after number portRetry; contact support if persistent
async function sendWithRetry(payload, attempt = 0) {
const res = await sendSMS(payload);
if (res.ok) return res;

const { code } = (await res.json()).error;

// Retry only on transient errors
if (['RATE_LIMITED', 'INTERNAL_ERROR'].includes(code) && attempt < 5) {
const delay = Math.min(2 ** attempt * 200, 5000); // exponential backoff
await new Promise(r => setTimeout(r, delay));
return sendWithRetry(payload, attempt + 1);
}

throw new Error(`SMS failed: ${code}`);
}
import time

TRANSIENT = {"RATE_LIMITED", "INTERNAL_ERROR"}

def send_with_retry(payload, attempt=0):
res = send_sms(payload)
if res.ok:
return res
code = res.json()["error"]["code"]
if code in TRANSIENT and attempt < 5:
time.sleep(min(2 ** attempt * 0.2, 5))
return send_with_retry(payload, attempt + 1)
raise RuntimeError(f"SMS failed: {code}")

๐Ÿ’ก Best Practicesโ€‹

  • Branch on code, not message โ€” messages may change, codes are stable.
  • Retry only transient errors (RATE_LIMITED, INTERNAL_ERROR) with backoff.
  • Treat 4xx as permanent โ€” fix the request rather than retrying.
  • Log timestamp and details to speed up support investigations.

Last Updated: May 2026 ยท Need help? Contact Support โ†’