API reference
Batches
Submit messages and read back what happened to them.
A 202 does not mean everything was accepted
Partial acceptance is a success. A batch where most numbers were invalid still returns 202 — always read accepted, rejected and rejections rather than branching on the status code alone.
Submit a batch
/public/v1/batchesPrices the batch, reserves the money and queues every accepted recipient. Returns 202: the batch is accepted and paid for, but nothing has been delivered yet.
Body parameters
senderstringRequiredThe name or number shown on the recipient's handset. Must be approved and operator-registered; check is_sendable on the sender-ids endpoint first.
max 32 characters
bodystringRequiredThe message template. May contain merge tags in braces, resolved per recipient from that recipient's attributes.
max 4000 characters, not blank after trimming
message_classstringRequiredDecides queue priority, whether quiet hours apply, which frequency caps are enforced and which suppression list is consulted.
TRANSACTIONAL | MARKETING | OTP
channelstringOptionalThe delivery channel.
SMS | WHATSAPP — default SMS
recipientsarrayRequiredWho to send to. Each entry carries a number and, optionally, the values for that recipient's merge tags.
1–100000 items
scheduled_atstringOptionalWhen to send. Must be in the future. A batch that ends up more than 30 minutes late is cancelled and refunded rather than sent.
ISO 8601, must be in the future
timezonestringOptionalThe timezone the schedule is expressed in. Only meaningful alongside scheduled_at.
IANA name, max 64 characters — defaults to the account timezone
is_testbooleanOptionalExcludes the batch from campaign statistics. It is still billed and still delivered — this is a reporting flag, not a sandbox.
default false
recipients.msisdnstringRequiredLocal or international form. Normalised to E.164 with Burkina Faso as the default region; a number that cannot be parsed becomes a rejection row rather than an error.
max 32 characters — local or E.164, default region BF
recipients.attributesobjectOptionalMerge-tag values for this recipient. A tag with no matching attribute substitutes to an empty string.
Request
Responses
Possible errors
| Code | Status | Meaning |
|---|---|---|
IDEMPOTENCY_KEY_REQUIRED | 422 | A batch was submitted without an Idempotency-Key header. |
VALIDATION_FAILED | 422 | The request was malformed: a missing or invalid parameter, a bad idempotency key length, a rejected webhook URL, or an unsupported method or content type. |
REQUEST_IN_PROGRESS | 409 | Another request with this idempotency key is still in flight. Wait for the stated interval and retry. |
IDEMPOTENCY_KEY_REUSED | 409 | This idempotency key was already used for a request with a different body. Use a new key. |
INSUFFICIENT_FUNDS | 402 | The prepaid wallet cannot cover the quoted amount. |
CREDIT_LIMIT_EXCEEDED | 402 | The postpaid accrual would exceed the account's credit limit. |
WALLET_IN_DEBIT | 402 | The wallet is in debit and cannot be spent from. |
BATCH_EMPTY | 422 | The batch contains no recipients. |
BATCH_BODY_EMPTY | 422 | The message body is blank once trimmed. |
SCHEDULE_INVALID | 422 | The scheduled time is in the past, or the timezone is not a valid IANA identifier. |
SENDER_NOT_APPROVED | 422 | The sender is not approved for this account. |
UNAUTHORIZED | 401 | No key, a malformed key, a wrong secret, a revoked or expired key, or a suspended account. All indistinguishable by design. |
INSUFFICIENT_SCOPE | 403 | The key is valid but does not carry the scope this endpoint requires. The scope is named in the detail. |
ACCOUNT_SUSPENDED | 403 | The account is suspended and cannot use the API. |
RATE_LIMITED | 429 | The key's request limit was exceeded. Wait for the interval in Retry-After. |
INTERNAL_ERROR | 500 | An unexpected failure on SMSend's side. Quote the request id to support. |
Retrieve a batch
/public/v1/batches/{batch_id}Reads the current state of a batch, including cumulative per-state counts and the full list of rejected recipients.
Path parameters
batch_idstringRequiredThe identifier returned when the batch was submitted.
26-character ULID
Request
Responses
Possible errors
| Code | Status | Meaning |
|---|---|---|
NOT_FOUND | 404 | No such resource — including one that belongs to another account, which is deliberately indistinguishable from one that never existed. |
UNAUTHORIZED | 401 | No key, a malformed key, a wrong secret, a revoked or expired key, or a suspended account. All indistinguishable by design. |
INSUFFICIENT_SCOPE | 403 | The key is valid but does not carry the scope this endpoint requires. The scope is named in the detail. |
RATE_LIMITED | 429 | The key's request limit was exceeded. Wait for the interval in Retry-After. |
INTERNAL_ERROR | 500 | An unexpected failure on SMSend's side. Quote the request id to support. |
Batches over 10,000 recipients
Above 10,000 recipients the submission is validated asynchronously, and the response comes back with a VALIDATING status and every count zeroed. The zeros mean "not computed yet", not "nothing was accepted".
The real figures arrive either from a later fetch of the batch or from a BATCH_ACCEPTED webhook. A batch left validating for 30 minutes is marked failed.
Rejection reasons
Each entry names the zero-based position of the recipient in the array you submitted, so you can map it back to your own data. The number is null when the input could not be normalised at all.
| Value | Meaning |
|---|---|
INVALID_MSISDN | The number could not be parsed, or is not valid for its region. |
DUPLICATE_IN_BATCH | The same normalised number appears more than once in this submission. |
UNROUTABLE_PREFIX | No operator owns the number's prefix. |
SUPPRESSED | The recipient has opted out of this class of message. |
DUPLICATE_RECENT_BODY | The identical rendered message was sent to this number within the last 24 hours. |
RATE_LIMIT_PER_MINUTE | The account's per-minute recipient ceiling was reached. |
RATE_LIMIT_PER_DAY | The account's daily recipient ceiling was reached. |
QUIET_HOURS | Marketing only. The send fell outside the account's permitted hours. |
FREQUENCY_CAP_DAILY | Marketing only. This recipient has already received the day's maximum. |
FREQUENCY_CAP_WEEKLY | Marketing only. This recipient has already received the week's maximum. |
The length of rejections always equals the rejected count. Warnings that annotate a recipient who was still sent to — an unrecognised merge tag, for instance — are deliberately excluded, so the two can never disagree.
Batch statuses
SETTLED is the only terminal state that means the work finished and the money is squared.
| Value | Meaning |
|---|---|
VALIDATING | Accepted but not yet priced or paid for. Only reachable on the asynchronous path, and expires after 30 minutes. |
SCHEDULED | Priced and reserved, waiting for its scheduled time. |
QUEUED | Priced and reserved, waiting for the router. |
SENDING | In flight. |
SETTLEDTerminal | Finished, with the money squared and any refund posted. |
REJECTEDTerminal | Every recipient was dropped before anything was reserved. |
FAILEDTerminal | Validation itself failed. |
CANCELLEDTerminal | Cancelled before or during sending, and refunded. |