Skip to content

Errors

Every 4xx and 5xx response has the same shape:

{
"error": {
"type": "validation_error",
"code": "missing_content",
"message": "content is required"
}
}
FieldUse it for
typeCoarse category. Branch on this for broad handling — auth vs. validation vs. billing.
codeThe contract. Stable, machine-readable. Branch on this for specific handling.
messageHuman-readable, may include your input. Log it; show it to developers. Never parse it, and never show it to end users.
const res = await fetch(url, opts);
const body = await res.json();
if (!res.ok) {
const { type, code, message } = body.error;
switch (code) {
case 'insufficient_balance':
case 'credit_limit_exceeded':
await alertFinanceTeam(message); // human problem — stop sending
break;
case 'sender_id_invalid':
await alertOps(message); // config problem — stop sending
break;
case 'invalid_destination':
await quarantineNumber(recipient); // data problem — skip this one, continue
break;
default:
if (res.status >= 500 || res.status === 429) {
await retryWithBackoff(); // transient — safe to retry
} else {
throw new Error(`${type}/${code}: ${message}`);
}
}
}

The important distinction is retryable vs. not. Retrying a missing_content will fail forever and just burn your rate limit. Retrying a 503 will very likely succeed.

typeHTTPMeaningRetry?
validation_error400Your request was malformed or missing a field.❌ Fix the request
authentication_error401Token missing, malformed, or revoked.❌ Fix the token
payment_required402Account can’t fund the request.❌ Top up first
not_found404The resource doesn’t exist.
pipeline_rejected400Accepted syntactically, rejected by routing/policy.❌ Usually
service_unavailable503A dependency is temporarily down.✅ With backoff
internal_error500Our fault.✅ With backoff

Your request is wrong. Fix it and resend — retrying unchanged will always fail.

CodeWhat happenedHow to fix it
invalid_request_bodyThe JSON didn’t parse, or a field had the wrong type.Check Content-Type: application/json and that to is an array.
missing_contentcontent was absent or empty.Provide non-empty content.
missing_recipientsto was absent or an empty array.Provide at least one destination.
missing_destinationA recipient entry was empty.Remove empty strings from to.
missing_sender_idfrom was absent.Provide an approved sender ID.
sender_id_invalidfrom is not approved for this account.Register it under Messaging → Sender IDs and wait for approval. See Sender IDs.
invalid_destinationAn MSISDN was malformed.Use international format — 2348020000000. See Phone numbers.
no_valid_recipientsEvery destination failed validation.Nothing was sent. Fix the numbers.
no_known_operatorsNo destination prefix matched a Nigerian network.Check the numbers are real Nigerian MSISDNs.
too_many_recipientsThe batch exceeded the per-request limit.Split into smaller batches.
missing_reference_idStatus was requested without a reference_id.Include it in the path.
CodeWhat happenedHow to fix it
unauthorizedToken missing, malformed, expired, or revoked.Send Authorization: Bearer <token>. Re-copy it from Settings → Security.
account_disabledThe account has been suspended.Contact your account manager — no code change will fix this.

Nothing was sent and nothing was charged. These need a human, not a retry.

CodeWhat happenedHow to fix it
insufficient_balanceBalance doesn’t cover the batch.Top up. Check cost_estimate.sufficient before sending to catch this early.
credit_limit_exceededPostpaid credit limit reached.Contact your account manager.
credit_limit_not_configuredPostpaid account with no limit set.Account setup issue — contact support.
tariff_not_configuredNo price configured for that operator.Contact support; we can’t price the message.

The request was valid but couldn’t be dispatched.

CodeWhat happenedHow to fix it
pipeline_rejectedRejected by a routing or policy rule.Read message for the specific rule. Often a blocked sender/content policy.
batch_not_foundNo batch with that reference_id.Check the ID. Note only batches sent via the REST API are visible here.

These are the retryable ones. Back off exponentially — and reuse your Idempotency-Key.

CodeWhat happenedHow to fix it
status_lookup_unavailableStatus store is temporarily unreachable.Retry in a few seconds. Your batch is unaffected.
redis_requiredA required dependency is down.Retry with backoff.
voice_unavailableThe voice pipeline is temporarily unavailable.Retry with backoff.
internal_errorUnhandled server error.Retry with backoff. If it persists, send us the reference_id.
reference_id_generation_failedCouldn’t mint a reference ID.Retry. Nothing was sent.

Exceed your account’s throughput and you’ll get 429. Back off and retry — honour Retry-After (in seconds) when present.

Batches are queued and dispatched FIFO at your account’s rate limit, so a large batch doesn’t need client-side throttling. It’s concurrent API calls that get limited, not messages within one batch. Prefer one request with 1,000 recipients over 1,000 requests with one recipient each — it’s faster, cheaper on your rate limit, and gives you a single reference_id to track.