eSMS AfricaeSMS Africa
API Reference

Messages API

Full reference for message send, status, and list endpoints.

POST /api/messages/send

Send a single SMS message.

Auth: Required

Request body

POST /api/messages/send
{
  "to": "+256712345678",
  "text": "Your OTP is 482910.",
  "sender_id": "MyApp",
  "route": "ESMS_UG"
}
FieldTypeRequiredDescription
tostringRecipient phone in E.164 format
textstringMessage content
sender_idstring-Approved sender ID. Defaults to route default
routestring-Route code e.g. ESMS_UG. Auto-detected if omitted
schedule_modestring-"now" (default) or "scheduled"
scheduled_atstringIf scheduledISO 8601 UTC datetime, at least 5 minutes in the future

Response 200

Response 200
{
  "id": "msg-a1b2c3d4",
  "status": "submitted",
  "segments": 1,
  "cost": 1.2022,
  "cost_currency": "KES",
  "route_cost": 35.00,
  "route_currency": "UGX",
  "route": "ESMS_UG",
  "balance_after": 1965.00,
  "scheduled_at": null
}

When schedule_mode is "scheduled", status will be "scheduled" and scheduled_at will contain the dispatch time. cost / cost_currency is the wallet deduction. route_cost / route_currency is the carrier's native price.

Errors

StatusDetail
400Phone cannot be parsed or is missing country code
400No active route for this country
400scheduled_at missing, less than 5 minutes in the future, or more than 7 days out
403Sender ID not found, not approved, or not approved for this country
422Wallet balance too low

POST /api/messages/bulk-precheck

Calculate cost of a bulk send before committing.

Auth: Required

Request body

{
  "contact_list_ids": [1, 2],
  "text": "Hi {{name}}, this is a test.",
  "sender_id": "MyApp"
}

Response 200

{
  "total_contacts": 250,
  "total_segments": 350,
  "total_cost": 1.88,
  "wallet_currency": "USD",
  "wallet_balance": 5000.00,
  "balance_after": 4998.12,
  "sufficient_balance": true,
  "countries": 2,
  "breakdown": [
    {
      "country_code": "UG",
      "country_name": "Uganda",
      "currency": "UGX",
      "route_code": "ESMS_UG",
      "contacts": 150,
      "segments": 200,
      "route_cost": 7000.00,
      "wallet_cost": 1.88,
      "price_per_segment": 35.00
    }
  ],
  "unroutable_count": 1,
  "unroutable": ["+000000000"]
}

POST /api/messages/send-bulk

Send a bulk SMS campaign.

Auth: Required

Request body

{
  "contact_list_ids": [1, 2],
  "text": "Hi {{name}}, your message here.",
  "sender_id": "MyApp",
  "schedule_mode": "now",
  "scheduled_at": null,
  "drip_rate": null
}
FieldTypeRequiredDescription
contact_list_idsarrayList IDs to send to
textstringMessage text with optional {{variables}}
sender_idstring-Approved sender ID
routestring-Force a specific route
schedule_modestring-now (default), scheduled, drip
scheduled_atstringIf scheduledISO 8601 UTC datetime
drip_rateintegerIf dripMessages per minute

Response 200

{
  "batch_id": "batch-uuid",
  "total_recipients": 250,
  "estimated_cost": 1.88,
  "status": "sending"
}

GET /api/messages

List messages with pagination and optional status filter.

Auth: Required

Query params

ParamDefaultDescription
page00-indexed page
limit201–100
status-Filter: queued, submitted, scheduled, dripping, delivered, failed

Response 200

{
  "messages": [
    {
      "id": "msg-a1b2c3d4",
      "phone": "+256712345678",
      "text": "Hello",
      "sender_id": "eSMSAfrica",
      "route": "ESMS_UG",
      "country": "UG",
      "segments": 1,
      "cost": 35.00,
      "currency": "UGX",
      "status": "delivered",
      "error_code": null,
      "retry_count": 0,
      "created_at": "2026-05-02T12:00:00Z",
      "delivered_at": "2026-05-02T12:00:05Z"
    }
  ],
  "total": 150,
  "page": 0,
  "limit": 20
}

GET /api/messages/:message_id

Get full message details including the event timeline.

Auth: Required

Response 200

{
  "id": "msg-a1b2c3d4",
  "phone": "+256712345678",
  "text": "Hello",
  "sender_id": "eSMSAfrica",
  "route": "ESMS_UG",
  "country": "UG",
  "segments": 1,
  "cost": 35.00,
  "currency": "UGX",
  "status": "delivered",
  "delivered_at": "2026-05-02T12:00:05Z",
  "timeline": [
    {
      "event": "accepted",
      "status": "queued",
      "detail": "API request received",
      "at": "2026-05-02T12:00:00Z",
      "metadata": { "source": "api_key" }
    },
    {
      "event": "submitted",
      "status": "submitted",
      "detail": "Gateway accepted",
      "at": "2026-05-02T12:00:01Z",
      "metadata": { "gateway_message_id": "0005e27f-6c0c" }
    },
    {
      "event": "delivered",
      "status": "delivered",
      "detail": "DLR: DELIVRD",
      "at": "2026-05-02T12:00:05Z"
    }
  ]
}

POST /api/messages/:message_id/retry

Retry a failed message.

Auth: Required

Only works on messages with status: "failed". Returns 409 if the message is not in a retryable state.

Response 200

{
  "id": "msg-a1b2c3d4",
  "status": "retrying",
  "retry_count": 1
}

GET /api/messages/stats

Dashboard KPIs - counts for today vs. yesterday, delivery rate, balance.

Auth: Required

Response 200

{
  "sent_today": 250,
  "sent_delta_pct": 5.5,
  "delivery_rate": 94.2,
  "failed_today": 2,
  "failed_delta": -1,
  "balance": 5000.50,
  "currency": "USD",
  "period_total": 5000,
  "period_days": 14
}

GET /api/messages/volume

Daily message volume for the last N days.

Auth: Required

Query params

ParamDefaultDescription
days141–90

Response 200

[
  {
    "date": "2026-05-02",
    "day": "Fri",
    "delivered": 1500,
    "failed": 10,
    "pending": 50
  }
]

On this page