Sendimo REST API v1 documentation.
Send SMS to Tunisian numbers with one HTTP call, track delivery and receive status updates by webhook. A REST API over HTTPS that takes and returns JSON.
Essentials
| Item | Value |
|---|---|
Base URL | https://api.sendimo.tn/v1 |
Protocol | HTTPS only (TLS 1.2+) |
Format | JSON, UTF-8, Content-Type: application/json |
Authentication | Authorization: Bearer sk_live_… (or sk_test_…) |
Versioning | Major version in the path (/v1). Non-breaking additions ship without a version bump. |
Timestamps | ISO 8601, UTC (2026-10-30T09:15:00Z) |
IDs | Prefixed strings: msg_…, bat_…, snd_… |
Money | String with 3 decimals, in TND ("0.140") |
Phone numbers | E.164 (+21620123456). Local 8-digit numbers are accepted and normalized to +216. |
v1 endpoints
| Method | Path | Purpose |
|---|---|---|
POST | /messages | Send one SMS |
POST | /messages/bulk | Send the same or personalized SMS to many numbers |
GET | /messages/{id} | Get one message and its status |
GET | /messages | List messages (filters + pagination) |
DELETE | /messages/{id} | Cancel a scheduled message |
GET | /batches/{id} | Get bulk send progress |
GET | /sender-ids | List your approved sender names |
POST | /sender-ids | Request a new sender name |
GET | /account/balance | Get remaining balance |
Authentication
Every request carries a secret API key as a Bearer token. Keys are created and revoked in the dashboard; each key is shown once at creation.
- Keys are server-side only. Never ship them in a mobile app or browser code.
- A missing or invalid key returns 401 unauthorized. A revoked key returns 401 key_revoked.
- Test numbers: in test mode, +21600000001 always ends delivered and +21600000002 always ends failed.
| Key prefix | Mode | Behavior |
|---|---|---|
sk_live_ | Live | Sends real SMS and charges the balance |
sk_test_ | Test | Validates and returns realistic responses; nothing is sent or charged. Webhooks still fire with simulated statuses. |
curl https://api.sendimo.tn/v1/account/balance \
-H "Authorization: Bearer sk_live_9f2c..."Send SMS — POST /messages
Sends one SMS. The call returns as soon as the message is accepted (202); the final status arrives later via webhook or GET /messages/{id}.
| Field | Type | Required | Description |
|---|---|---|---|
to | string | Yes | Recipient, E.164 or 8-digit local |
from | string | Yes | An approved sender ID (e.g. SENDIMO) |
text | string | Yes | Message body, max 1,530 GSM-7 chars or 670 Unicode chars (10 segments) |
scheduled_at | string | No | ISO 8601 time to send later, up to 30 days ahead |
reference | string | No | Your own ID (max 64 chars), echoed back in responses and webhooks |
webhook_url | string | No | Overrides the account webhook for this message |
curl -X POST https://api.sendimo.tn/v1/messages \
-H "Authorization: Bearer $SENDIMO_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-4821-otp" \
-d '{
"to": "+21620123456",
"from": "SENDIMO",
"text": "Your code is 482913. It expires in 5 minutes.",
"reference": "order-4821"
}'{
"id": "msg_01HF8Z3K9QX2",
"status": "queued",
"to": "+21620123456",
"from": "SENDIMO",
"text": "Your code is 482913. It expires in 5 minutes.",
"encoding": "GSM-7",
"segments": 1,
"network": "ooredoo",
"cost": { "amount": "0.140", "currency": "TND" },
"reference": "order-4821",
"scheduled_at": null,
"created_at": "2026-10-30T09:15:00Z"
}Bulk send — POST /messages/bulk
Sends up to 10,000 messages in one call. Use a shared text, or per-recipient text for personalized messages. Returns a batch you can track.
Invalid numbers do not fail the whole batch: they are listed in rejected with { "index", "to", "error" } and are not charged. GET /batches/{id} returns the same object with live counts per status (queued, sent, delivered, failed).
| Field | Type | Required | Description |
|---|---|---|---|
from | string | Yes | Approved sender ID, used for every message |
text | string | No* | Shared body for all recipients. *Required unless every item has its own text |
messages | array | Yes | List of { "to", "text"?, "reference"? }; a per-item text overrides the shared one |
scheduled_at | string | No | Send the whole batch later |
reference | string | No | Your ID for the batch |
{
"from": "SENDIMO",
"text": "Promo -20% ce week-end sur tout le magasin.",
"messages": [
{ "to": "+21620123456" },
{ "to": "+21698765432", "text": "Salut Amine, -20% pour toi ce week-end." }
]
}{
"id": "bat_01HF90AB7C",
"status": "processing",
"total": 2,
"accepted": 2,
"rejected": [],
"estimated_cost": { "amount": "0.280", "currency": "TND" },
"created_at": "2026-10-30T09:20:00Z"
}Get and list messages
GET /messages/{id} returns the message object with its current status, plus sent_at, delivered_at and error (null unless failed).
GET /messages lists messages, newest first, with cursor pagination.
| Query param | Example | Description |
|---|---|---|
status | failed | Filter by status |
to | +21620123456 | Filter by recipient |
batch_id | bat_01HF90AB7C | Messages from one bulk send |
reference | order-4821 | Your own ID |
created_after / created_before | 2026-10-01T00:00:00Z | Date range |
limit | 50 | 1–100, default 20 |
cursor | eyJpZCI6... | Value of next_cursor from the previous page |
{
"data": [ { "id": "msg_01HF8Z3K9QX2", "status": "delivered", "...": "..." } ],
"has_more": true,
"next_cursor": "eyJpZCI6Im1zZ18wMUhGOFoz..."
}Message statuses
A message moves queued → sent → delivered, or ends in failed, undelivered or canceled. Final statuses never change. To cancel a scheduled message: DELETE /messages/{id} (only while scheduled).
| Status | Final | Meaning | Charged |
|---|---|---|---|
scheduled | No | Waiting for scheduled_at; can be canceled | No |
queued | No | Accepted, waiting to go to the operator | Reserved |
sent | No | Handed to the operator (Ooredoo, Orange, Tunisie Telecom) | Yes |
delivered | Yes | Operator confirmed delivery to the handset | Yes |
undelivered | Yes | Operator could not deliver (phone off too long, invalid subscriber) | Yes |
failed | Yes | Rejected before reaching the operator; see error.code | No, refunded |
canceled | Yes | Scheduled message canceled by you | No |
Sender IDs
A sender ID is the name recipients see instead of a number (max 11 letters/digits, e.g. SENDIMO). Tunisian operators require each one to be approved before use, so new IDs go through review.
Statuses: pending, approved, rejected (with a reason). Sending with an ID that is not approved returns 403 sender_not_approved.
{
"data": [
{ "id": "snd_01HE7A", "name": "SENDIMO", "status": "approved", "created_at": "2026-10-01T10:00:00Z" },
{ "id": "snd_01HF2C", "name": "MYSHOP", "status": "pending", "created_at": "2026-10-29T14:30:00Z" }
]
}{ "name": "MYSHOP", "use_case": "Order confirmations and delivery updates for myshop.tn" }Balance — GET /account/balance
reserved is held for queued messages and released or charged once they reach a status. A send that exceeds the available balance returns 402 insufficient_balance.
{
"balance": { "amount": "152.340", "currency": "TND" },
"reserved": { "amount": "0.900", "currency": "TND" },
"low_balance_threshold": { "amount": "20.000", "currency": "TND" }
}Errors
Every error uses the same shape, with a stable machine-readable code and a request_id for support.
| HTTP | Code | Cause |
|---|---|---|
400 | invalid_request | Malformed JSON or missing field |
400 | invalid_phone_number | to is not a valid number |
400 | text_too_long | More than 10 segments |
401 | unauthorized / key_revoked | Bad, missing or revoked API key |
402 | insufficient_balance | Not enough balance for the send |
403 | sender_not_approved | from is not an approved sender ID |
404 | not_found | Unknown message, batch or sender ID |
409 | idempotency_conflict | Same Idempotency-Key reused with a different body |
422 | destination_not_supported | Number outside supported countries (v1: Tunisia only) |
429 | rate_limited | Too many requests; see Retry-After |
500 / 503 | internal_error / unavailable | Our side; safe to retry with the same idempotency key |
{
"error": {
"code": "invalid_phone_number",
"message": "'to' must be a valid Tunisian number in E.164 format.",
"field": "to",
"request_id": "req_01HF9C4D"
}
}Rate limits
Each response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. A 429 includes Retry-After in seconds.
| Scope | Limit |
|---|---|
All endpoints, per API key | 100 requests / second |
POST /messages/bulk | 10 requests / minute |
Sending throughput | 50 SMS / second per account (higher on request) |
Idempotency
Send an Idempotency-Key header (any unique string, max 64 chars) on every POST. If a network error leaves you unsure whether a send worked, retry with the same key: Sendimo returns the original response instead of sending twice. Keys are kept 24 hours.
Encoding and segments
Sendimo picks the encoding automatically and bills per segment. Arabic text, emoji and some accented characters force Unicode, which roughly halves the characters per SMS.
The response always returns encoding, segments and cost, so clients can show the price before a bulk send.
| Encoding | 1 segment | Each segment when split | Typical content |
|---|---|---|---|
GSM-7 | 160 chars | 153 chars | Latin text, French without most accents |
Unicode (UCS-2) | 70 chars | 67 chars | Arabic, emoji, characters outside GSM-7 |