Skip to content
Sendozi

Reliability

Errors and the response envelope

Every response has success, then data or error, and a request_id. An error carries a stable code, a message and a resolution. 4xx codes are answers about your request and will repeat if resent unchanged; 429 and 5xx are worth retrying with the same Idempotency-Key.

By SendoziUpdated 4 min read

The response envelope

Success and failure
Success
{ "success": true, "data": { }, "request_id": "req_8ecdcb4ce2ae4290" }
Failure
{
  "success": false,
  "error": {
    "code": "insufficient_balance",
    "message": "Wallet balance is not enough for this send.",
    "resolution": "Fund the wallet and retry."
  },
  "request_id": "req_8ecdcb4ce2ae4290"
}
429
HTTP/1.1 429 Too Many Requests
Retry-After: 23

{
  "success": false,
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Too many API send requests for this workspace.",
    "resolution": "Try again in about 23 seconds.",
    "retry_after_seconds": 23
  },
  "request_id": "req_2f1e0d9c8b7a6958"
}
  • code is stable. Branch on it, never on message or resolution, which may be reworded.
  • resolution is written for your developers, not for your end users.
  • retry_after_seconds and the Retry-After header come with rate-limit refusals.
  • request_id is also sent as the x-request-id header. Log it with every call. To use your own, send X-Request-ID matching [A-Za-z0-9_-]{8,64}; anything else is replaced.

Error codes

Keys, access and the request itself
CodeStatusMeaning and fix
unauthorized401No key, or an unrecognised key. Send Authorization: Bearer sk_...
api_key_blocked403The key is revoked, expired or blocked. Use an active key
insufficient_scope403The key lacks the scope this endpoint needs. Create a key with it
ip_not_allowed403A live key used from an address that is not on your IP allowlist. The message names the address; call from an allowed server or add it in the Console
workspace_suspended403The workspace cannot send. Contact Sendozi
invalid_request400A missing or malformed field, invalid JSON, a message over the length limit, or an invalid cursor. The message says which
payload_too_large413The body is over 32 MB. Split the recipients across requests
too_many_recipients400More than 1,000,000 recipients in one request
not_found404No such endpoint, or no such resource in your workspace
method_not_allowed405The endpoint does not accept this HTTP method
Keys, access and the request itself
Send refusals. Nothing is sent or charged
CodeStatusMeaning and fix
invalid_recipient400No valid Nigerian mobile number in the request
recipient_suppressed400Every recipient has opted out of your SMS
recipient_dnd400Live promotional send where every recipient returned a Do Not Disturb block to your workspace in the last 90 days
message_policy_violation400The content matched a blocked phrase. Change the wording
kyc_required403Live sending needs approved KYC and production access
sender_id_not_approved403The Sender ID is not approved for live SMS
sender_id_route_mismatch403A transactional send on a Sender ID approved for promotional only
sender_id_airtel_not_whitelisted403A promotional send on a Transactional Sender ID whose every recipient is on Airtel, which has not yet whitelisted it for promotional
sender_id_unclassified403The Sender ID has no route yet; Sendozi classifies it, or submit a new request
channel_not_active403SMS is not active for the workspace, or sandbox is disabled platform-wide
insufficient_balance403The wallet does not cover the estimated cost. Fund it and retry
wallet_frozen403The wallet is frozen. Contact Sendozi
Send refusals. Nothing is sent or charged
Transactional sends
CodeStatusMeaning and fix
transactional_idempotency_required400Send an Idempotency-Key
transactional_bulk_not_allowed400Exactly one recipient per transactional request
rate_limit_exceeded429The workspace's transactional limit per minute or per day is reached; wait for Retry-After
Transactional sends
Conflicts, limits and outages
CodeStatusMeaning and fix
idempotency_conflict409The key was used with a different body, or the first request is still running. See idempotency
batch_not_cancellable409The batch has already finished or been cancelled
rate_limit_exceeded429Over a rate limit. Wait for Retry-After
queue_unavailable503The send could not be queued; nothing was sent or charged. Retry with the same key
wallet_unavailable503The wallet could not be reserved. Retry shortly
provider_not_configured503Sending is paused for maintenance, or a platform dependency is unavailable. Retry with backoff
DELIVERY_TEMPORARILY_UNAVAILABLE503SMS delivery is temporarily unavailable. Retry shortly
internal_error500An unexpected failure. Retry once, then contact support with the request_id
Conflicts, limits and outages

Codes about a message's delivery, such as INVALID_RECIPIENT or delivery_expired, are not request errors. They appear on the message itself; see why a message failed.

What to retry

Retry
Network errors, timeouts, 429 (after Retry-After), and 500, 502, 503 and 504, with the same Idempotency-Key and exponential backoff.
Fix, then send
Every other 4xx. Resending unchanged returns the same error.
Wait for the first
409 idempotency_conflict with the message "still being processed": the original request is running. Retry in a few seconds.
Branch on status, then on code
Node.js
export function classify(status, error) {
  if (status === 429 || status >= 500) return "retry";
  if (error.code === "insufficient_balance") return "fund-wallet";
  if (error.code.startsWith("sender_id_")) return "fix-sender-id";
  return "fix-request";
}

Asking for help

Include the request_id, the endpoint, the time and the error code. For a delivery question, include the message_id or batch_id. The request_id is how a request is found in Sendozi's logs.

Frequently asked questions

What does insufficient_balance mean?
The wallet balance does not cover the estimated cost of the whole send, so nothing was queued or charged. Fund the wallet and send again.
Which Sendozi errors should I retry?
Network errors, timeouts, 429 after the Retry-After delay, and 5xx responses, each with the same Idempotency-Key. Other 4xx errors will repeat until the request is fixed.
Why did my send fail with provider_not_configured?
Sending is paused for maintenance or a platform dependency is briefly unavailable. It is temporary: retry with backoff and the same Idempotency-Key.
What is request_id for?
It identifies one API request in Sendozi's logs. It is in every response body and in the x-request-id header. Quote it when you contact support.

Make retries safe

Pair error handling with an Idempotency-Key so a retry never sends twice.