SendaiDocs

API reference

View as Markdown

SMS

The message resource — every attribute of the message object, then the three operations on it, send, retrieve, and bulk send.

A message is one SMS to one recipient. Sending creates it, the carrier's confirmation completes it, and its id reads it back at any point in between — a bulk send is simply many messages created in one call, each with its own id and its own delivery signal.

One convention to hold onto throughout: POST /api/v1/sms returns the message object bare, while every other operation wraps its payload in the { status, message, data } envelope — see two response conventions.

The message object

The same object comes back from a send and from a retrieve; only the envelope around it differs. Fresh from a send it reads status: enqueued, delivered_at: null; the carrier's confirmation later fills delivered_at.

Attributes

  • idstring (uuid)
    Message id. Pass it to GET /api/v1/sms/{id}.
  • tostring
    Recipient MSISDN in international format.
  • messagestring
    The body as sent.
  • sender_idstring
    Display name of the approved sender the recipient saw. Note the name: the field you send is from; the field that comes back is sender_id.
  • operatorstring
    Carrier derived from the recipient MSISDN: econet, netone, or telecel.
  • chargestring | integer
    Amount billed, in ten-thousandths of charge_currency (10000 == 1.00, so 450 == 0.045). Serialised as a string on the single send and retrieve, as an integer on bulk items — same unit, different JSON type. See billing and segments.
  • charge_currencystring
    Currency of charge.
  • operator_responsestring
    Raw response from the carrier. Empty until dispatched; DELIVRD is a typical confirmed value.
  • received_atstring (date-time)
    When Sendai accepted the message.
  • statusstring
    The send pipeline: createdenqueuedprocessingsuccess, or failed. success means the carrier accepted or confirmed it — it is not the delivery signal; there is no separate delivery-status field.
  • created_atstring (date-time)
    When the message row was created.
  • delivered_atstring (date-time) | null
    null until the carrier confirms handset delivery, then an RFC 3339 timestamp. This is the only delivery signal. A message can sit at status: success with delivered_at: null for a while — normal, not a failure.
The message object
Message
{
  "id": "0f9c1b3a-6d2e-4a1b-9c3d-7e5f2a8b4c10",
  "to": "263771000000",
  "message": "Your code is 4821",
  "sender_id": "Sendai SMS",
  "operator": "econet",
  "charge": "450",
  "charge_currency": "USD",
  "operator_response": "DELIVRD",
  "received_at": "2026-07-06T09:12:03Z",
  "status": "success",
  "created_at": "2026-07-06T09:12:03Z",
  "delivered_at": "2026-07-06T09:12:44Z"
}

Send an SMS

POST/api/v1/sms

Sends one message. Sendai validates the sender ID, prices the message per segment, places a hold on your prepaid balance, and answers 202 Accepted — accepted, priced, and queued, not delivered. Retrieve the message or register a webhook for the delivery signal.

Request body

  • tostringrequired
    Recipient MSISDN in international format, e.g. 263771000000.
  • fromstringrequired
    An approved sender ID on your account — list yours with Sender IDs. An unapproved value is rejected with 400 unknown or unapproved sender id.
  • messagestringrequired
    Message body. Billed per GSM-7/UCS-2 segment — see billing and segments.

Response

202 returns the message object bare — this is the one endpoint with no { status, message, data } envelope, so there is no data to unwrap. Two fields to note:

  • The sender comes back as sender_id, not from.
  • charge is a string here ("450" == 0.045 charge_currency); the bulk operation returns the same value as an integer.

status: enqueued and delivered_at: null are the normal state of a fresh send — delivery confirmation arrives asynchronously.

Errors

StatusWhen
400Malformed JSON, or a from that is not an approved sender ID. Body carries error.type.
401Missing, malformed, or revoked API key. Body carries error.code.
RequestPOST /api/v1/sms
curl -X POST https://api.sendai.co.zw/api/v1/sms \
  -H "Authorization: Bearer $SENDAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "263771000000",
    "from": "Sendai SMS",
    "message": "Your code is 4821"
  }'
Response
202 Accepted
{
  "id": "0f9c1b3a-6d2e-4a1b-9c3d-7e5f2a8b4c10",
  "to": "263771000000",
  "message": "Your code is 4821",
  "sender_id": "Sendai SMS",
  "operator": "econet",
  "charge": "450",
  "charge_currency": "USD",
  "operator_response": "",
  "received_at": "2026-07-06T09:12:03Z",
  "status": "enqueued",
  "created_at": "2026-07-06T09:12:03Z",
  "delivered_at": null
}

Retrieve an SMS

GET/api/v1/sms/{id}

Reads back one message by the id from the send response. Unlike the send operation, this response is enveloped — the message sits under data.

Path parameters

  • idstring (uuid)required
    The message id from the send or bulk send response.

Response

Two fields track the journey, and they are two separate facts:

  • data.status — the send pipeline: createdenqueuedprocessingsuccess, or failed.
  • data.delivered_atnull until the carrier confirms delivery, then a timestamp. This is the only delivery signal.

A message can sit at status: success with delivered_at: null for a while — the pipeline finished but the carrier has not confirmed yet. That is normal, not a failure. Carrier confirmation takes seconds to hours, so poll on an interval, or let a webhook call you instead.

operator_response is the raw carrier response — empty until dispatched.

Errors

StatusWhen
400id is not a valid UUID. Body carries error.type.
401Missing, malformed, or revoked API key. Body carries error.code.
404No such message on your account — unknown id, another account's message, or not an SMS.
RequestGET /api/v1/sms/{id}
curl https://api.sendai.co.zw/api/v1/sms/0f9c1b3a-6d2e-4a1b-9c3d-7e5f2a8b4c10 \
  -H "Authorization: Bearer $SENDAI_API_KEY"
Response
200 OK
{
  "status": "success",
  "message": "request successful",
  "data": {
    "id": "0f9c1b3a-6d2e-4a1b-9c3d-7e5f2a8b4c10",
    "to": "263771000000",
    "message": "Your code is 4821",
    "sender_id": "Sendai SMS",
    "operator": "econet",
    "charge": "450",
    "charge_currency": "USD",
    "operator_response": "DELIVRD",
    "received_at": "2026-07-06T09:12:03Z",
    "status": "success",
    "created_at": "2026-07-06T09:12:03Z",
    "delivered_at": "2026-07-06T09:12:44Z"
  }
}

Send bulk SMS

POST/api/v1/sms/bulk

Sends the same body to every recipient in to, priced as one send. The whole send is accepted or rejected together — there is no partial success. Returns one entry per recipient, in request order; each items[].id behaves exactly like a single-send id, so you track each recipient with Retrieve an SMS.

Request body

  • toarray of stringsrequired
    Recipient MSISDNs in international format. At least one. A repeated MSISDN is not collapsed — the recipient gets two messages and you are billed for both.
  • fromstringrequired
    An approved sender ID on your account — list yours with Sender IDs.
  • messagestringrequired
    Body sent to every recipient. Billed per segment, per recipient.
  • webhook_urlstring (url)
    Optional callback applied to every message in the send. Must be a valid URL. See Webhooks for the account-level alternative.

Response

This response is enveloped. data.charge is the total held for the send and data.items[].charge what each recipient cost — operators price independently, so entries within one send can differ.

charge is an integer here, unlike the string the single send returns. Both are in ten-thousandths of the currency (450 == 0.045); only the JSON type differs. Note the total's currency arrives as currency, not charge_currency.

Errors

StatusWhen
400Validation failed — to empty, message or from blank, or webhook_url not a URL. Offending fields are listed in error.validation_errors.
401Missing, malformed, or revoked API key.
422insufficient balance — your prepaid balance does not cover the send. Nothing is sent. Check Balances first.
RequestPOST /api/v1/sms/bulk
curl -X POST https://api.sendai.co.zw/api/v1/sms/bulk \
  -H "Authorization: Bearer $SENDAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["263771000000", "263772000001"],
    "from": "Sendai SMS",
    "message": "Polls close at 18:00. Thanks for taking part!",
    "webhook_url": "https://example.com/webhooks/dlr"
  }'
Response
202 Accepted
{
  "status": "success",
  "message": "bulk sms enqueued",
  "data": {
    "total": 2,
    "charge": 900,
    "currency": "USD",
    "items": [
      {
        "id": "0f9c1b3a-6d2e-4a1b-9c3d-7e5f2a8b4c10",
        "to": "263771000000",
        "status": "enqueued",
        "operator": "econet",
        "charge": 450,
        "charge_currency": "USD"
      },
      {
        "id": "7e5f2a8b-4c10-4a1b-9c3d-0f9c1b3a6d2e",
        "to": "263772000001",
        "status": "enqueued",
        "operator": "netone",
        "charge": 450,
        "charge_currency": "USD"
      }
    ]
  }
}