API v1 referenceAvailable

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

ItemValue
Base URLhttps://api.sendimo.tn/v1
ProtocolHTTPS only (TLS 1.2+)
FormatJSON, UTF-8, Content-Type: application/json
AuthenticationAuthorization: Bearer sk_live_… (or sk_test_…)
VersioningMajor version in the path (/v1). Non-breaking additions ship without a version bump.
TimestampsISO 8601, UTC (2026-10-30T09:15:00Z)
IDsPrefixed strings: msg_…, bat_…, snd_…
MoneyString with 3 decimals, in TND ("0.140")
Phone numbersE.164 (+21620123456). Local 8-digit numbers are accepted and normalized to +216.

v1 endpoints

MethodPathPurpose
POST/messagesSend one SMS
POST/messages/bulkSend the same or personalized SMS to many numbers
GET/messages/{id}Get one message and its status
GET/messagesList messages (filters + pagination)
DELETE/messages/{id}Cancel a scheduled message
GET/batches/{id}Get bulk send progress
GET/sender-idsList your approved sender names
POST/sender-idsRequest a new sender name
GET/account/balanceGet 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 prefixModeBehavior
sk_live_LiveSends real SMS and charges the balance
sk_test_TestValidates and returns realistic responses; nothing is sent or charged. Webhooks still fire with simulated statuses.
ExamplecURL
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}.

FieldTypeRequiredDescription
tostringYesRecipient, E.164 or 8-digit local
fromstringYesAn approved sender ID (e.g. SENDIMO)
textstringYesMessage body, max 1,530 GSM-7 chars or 670 Unicode chars (10 segments)
scheduled_atstringNoISO 8601 time to send later, up to 30 days ahead
referencestringNoYour own ID (max 64 chars), echoed back in responses and webhooks
webhook_urlstringNoOverrides the account webhook for this message
RequestcURL
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"
  }'
Response 202 AcceptedJSON
{
  "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).

FieldTypeRequiredDescription
fromstringYesApproved sender ID, used for every message
textstringNo*Shared body for all recipients. *Required unless every item has its own text
messagesarrayYesList of { "to", "text"?, "reference"? }; a per-item text overrides the shared one
scheduled_atstringNoSend the whole batch later
referencestringNoYour ID for the batch
Request bodyJSON
{
  "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." }
  ]
}
Response 202 AcceptedJSON
{
  "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 paramExampleDescription
statusfailedFilter by status
to+21620123456Filter by recipient
batch_idbat_01HF90AB7CMessages from one bulk send
referenceorder-4821Your own ID
created_after / created_before2026-10-01T00:00:00ZDate range
limit501–100, default 20
cursoreyJpZCI6...Value of next_cursor from the previous page
ResponseJSON
{
  "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).

StatusFinalMeaningCharged
scheduledNoWaiting for scheduled_at; can be canceledNo
queuedNoAccepted, waiting to go to the operatorReserved
sentNoHanded to the operator (Ooredoo, Orange, Tunisie Telecom)Yes
deliveredYesOperator confirmed delivery to the handsetYes
undeliveredYesOperator could not deliver (phone off too long, invalid subscriber)Yes
failedYesRejected before reaching the operator; see error.codeNo, refunded
canceledYesScheduled message canceled by youNo

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.

GET /sender-idsJSON
{
  "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" }
  ]
}
POST /sender-idsJSON
{ "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.

ResponseJSON
{
  "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.

HTTPCodeCause
400invalid_requestMalformed JSON or missing field
400invalid_phone_numberto is not a valid number
400text_too_longMore than 10 segments
401unauthorized / key_revokedBad, missing or revoked API key
402insufficient_balanceNot enough balance for the send
403sender_not_approvedfrom is not an approved sender ID
404not_foundUnknown message, batch or sender ID
409idempotency_conflictSame Idempotency-Key reused with a different body
422destination_not_supportedNumber outside supported countries (v1: Tunisia only)
429rate_limitedToo many requests; see Retry-After
500 / 503internal_error / unavailableOur side; safe to retry with the same idempotency key
Error formatJSON
{
  "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.

ScopeLimit
All endpoints, per API key100 requests / second
POST /messages/bulk10 requests / minute
Sending throughput50 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.

Encoding1 segmentEach segment when splitTypical content
GSM-7160 chars153 charsLatin text, French without most accents
Unicode (UCS-2)70 chars67 charsArabic, emoji, characters outside GSM-7

Other integrations

Ready to send your SMS?

Launching soon in Tunisia. Join early access.