eSMS AfricaeSMS Africa
API ReferenceSMS APITwo-Way SMS

Messages

POST /v1/two-way/messages sends FROM your keyword's short code and opens the conversation window; GET /v1/two-way/messages lists inbound and outbound messages with cursor pagination.

Send a message

POST https://sms.esmsafrica.io/v1/two-way/messages

Sends text to to from your keyword's short code. Use it to start a conversation or to continue one. A successful send records conversation affinity: for the affinity window (72 hours by default) the recipient's plain replies on a shared code, such as 1, 2 or YES, are delivered to this keyword. Scope: two_way or send.

Request body

FieldTypeRequiredDescription
keywordstringOne of keyword / service_idYour keyword, any case ("CLINIC").
service_idintegerOne of keyword / service_idYour service ID.
codestringNoThe short code. Only needed when you hold the same keyword on two codes.
tostringYesRecipient in international format, e.g. +2348012345678. Must be in the code's country.
textstringYes1 to 1,600 characters, at most 10 segments. GSM-7 is 160 characters per single segment (153 per part); Unicode is 70 (67 per part).
environmentstringNo"live" (default with a live key) or "sandbox": deliver to a simulated phone in the sandbox instead. An esms_test_ key is always sandbox.

Rules:

  • The keyword must be active or past_due (sandbox sends also work for pending).
  • Recipients who opted out of your keyword (or texted a bare STOP to the code) are refused with 403 recipient_opted_out.
  • Each segment is charged at your outbound price: a price set on your keyword, else your account's own price for that country, else the country's two-way outbound price (the Basic SMS price unless a custom two-way price is set).
  • If the network rejects the message at submission, status is failed, cost is 0 and the charge is refunded to your wallet automatically; no conversation window is opened.

Response

{
  "id": "6f1d2c3b-4a59-4e7d-8c6b-5a4f3e2d1c0b",
  "status": "submitted",
  "segments": 1,
  "cost": 0.0068,
  "currency": "USD",
  "balance_after": 41.27,
  "service_id": 42,
  "keyword": "CLINIC",
  "code": "3450",
  "to": "+2348012345678",
  "conversation_id": 981
}
FieldDescription
idMessage ID. Delivery reports for it arrive like any other SMS.
statussubmitted (accepted by the network) or failed (refunded).
segmentsBillable segments.
cost / currencyAmount charged, in your wallet currency. 0 when failed.
balance_afterWallet balance after the charge.
service_id / keyword / codeThe keyword and short code it was sent from.
conversation_idThe conversation this send opened or extended (null when failed).

With an esms_test_ key or "environment": "sandbox", the response has "sandbox": true, "environment": "sandbox", "status": "delivered", "cost": 0.0, would_cost (what live would charge), encoding and affinity_expires_at. See Sandbox.

Examples

curl -X POST "https://sms.esmsafrica.io/v1/two-way/messages" \
  -H "Authorization: Bearer $ESMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"keyword": "CLINIC", "to": "+2348012345678", "text": "Reply 1 Emergency, 2 Maternity, 3 Pharmacy, 4 Talk to a nurse"}'

Errors

400 invalid_phone, 400 invalid_recipient_country, 402 insufficient_balance, 403 recipient_opted_out, 403 account_suspended, 404 not_found (no such keyword), 409 service_not_active, 409 number_unavailable, 409 ambiguous_keyword, 413 message_too_long. See Errors.

List messages

GET https://sms.esmsafrica.io/v1/two-way/messages

Your two-way messages, newest first, cursor paginated. Inbound only by default. Scope: two_way or query.

Query parameters

ParameterTypeDescription
service_id / keywordinteger / stringOnly this keyword. Omit both for all your keywords.
codestringWith keyword, when you hold it on two codes.
phonestringOnly messages with this number.
directionstringinbound (default), outbound or all.
since / untilstringISO 8601 date or datetime (until is exclusive).
cursorstringnext_cursor from the previous page.
limitinteger1 to 200, default 50.

Response

{
  "data": [
    {
      "id": "a3c5e8f0-6b1d-4c2e-9f7a-1d2e3f4a5b6c",
      "service_id": 42,
      "keyword": "CLINIC",
      "code": "3450",
      "direction": "inbound",
      "from": "+2348012345678",
      "to": "3450",
      "phone": "+2348012345678",
      "text": "2",
      "body_without_keyword": "2",
      "matched_by": "affinity",
      "status": "received",
      "billed": true,
      "cost": 0.0068,
      "segments": 1,
      "conversation_id": 981,
      "created_at": "2026-10-12T09:32:10+00:00"
    }
  ],
  "has_more": true,
  "next_cursor": "18342"
}
FieldDescription
directioninbound (customer to you) or outbound (you to customer).
from / toThe customer's number and the short code, in the direction of travel.
phoneThe customer's number, whatever the direction.
textThe full message as sent or received.
body_without_keywordInbound only: the text with your keyword removed. null for outbound.
matched_byInbound only: keyword, affinity, dedicated or admin.
statusreceived for inbound; the delivery status (submitted, delivered, failed ...) for outbound.
billedtrue charged, false stored unbilled (low balance), null not billable.
cost / segmentsAmount charged and billable segments.
conversation_idThe conversation this message belongs to.

Examples

curl "https://sms.esmsafrica.io/v1/two-way/messages?keyword=CLINIC&direction=all&since=2026-10-01&limit=50" \
  -H "Authorization: Bearer $ESMS_API_KEY"

Reply (legacy)

POST https://sms.esmsafrica.io/v1/two-way/services/{service_id}/reply

The original reply endpoint. It still works and behaves exactly like Send a message for that service, including billing, opt-out checks, conversation affinity and the sandbox switch. New integrations should use POST /v1/two-way/messages. Scope: two_way or send.

FieldTypeRequiredDescription
tostringYesRecipient in international format.
textstringYes1 to 1,600 characters.
environmentstringNo"live" or "sandbox".

The response is the same as Send a message.

curl -X POST "https://sms.esmsafrica.io/v1/two-way/services/42/reply" \
  -H "Authorization: Bearer $ESMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to": "+2348012345678", "text": "Thanks - a nurse will call you shortly."}'

Service message history

GET https://sms.esmsafrica.io/v1/two-way/services/{service_id}/messages

The latest messages for one service, oldest first (legacy shape, no cursor). Prefer List messages or conversations for new code. Scope: two_way or query.

ParameterDescription
limit1 to 1,000, default 200.
phoneOnly messages with this number.
{
  "messages": [
    {
      "id": "a3c5e8f0-6b1d-4c2e-9f7a-1d2e3f4a5b6c",
      "direction": "inbound",
      "from": "+2348012345678",
      "to": "3450",
      "phone": "+2348012345678",
      "text": "CLINIC hi",
      "keyword": "CLINIC",
      "status": "received",
      "billed": true,
      "cost": 0.0068,
      "auto_reply": false,
      "created_at": "2026-10-12T09:30:00+00:00"
    }
  ],
  "total": 128
}

auto_reply is true on outbound messages sent by your keyword's auto-reply.

curl "https://sms.esmsafrica.io/v1/two-way/services/42/messages?phone=%2B2348012345678&limit=100" \
  -H "Authorization: Bearer $ESMS_API_KEY"

On this page