API reference
API overview
Base URLs, authentication, the two response conventions, error shapes, and how billing and delivery actually work — read this before writing a client.
The Sendai API is plain JSON over HTTPS, authenticated with an API key. This page covers the conventions that apply to every endpoint; each operation then has its own page with the exact request and response.
Base URLs
| Host | Use |
|---|---|
https://api.sendai.co.zw | Production |
https://staging-api.sendai.co.zw | Staging — a build target for integration work |
All paths in this reference are relative to the production host.
Authentication
One mechanism: an API key as a bearer token on every request.
Authorization: Bearer sk_live_4c8e…
Create keys in the dashboard under Settings → Developer, or with
POST /api/v1/api-tokens. A key is valid until you revoke it. Full detail,
including zero-downtime rotation, is in Authentication.
Two response conventions
This is the one thing to get right before you write a client. POST /api/v1/sms returns the
message object bare, with no wrapper:
{
"id": "0f9c1b3a-6d2e-4a1b-9c3d-7e5f2a8b4c10",
"to": "263771000000",
"status": "enqueued",
"delivered_at": null
}
Every other endpoint wraps its payload in an envelope, with the payload under data:
{
"status": "success",
"message": "request successful",
"data": { }
}
So GET /api/v1/sms/{id} gives you data.status, while POST /api/v1/sms gives you status
at the top level. Unwrapping .data on a send returns undefined.
Errors
Branch on the HTTP status code — the error body comes in two shapes depending on the endpoint.
The two SMS read/write endpoints (POST /api/v1/sms, GET /api/v1/sms/{id}) return an
error.type:
{
"status": "",
"message": "not found",
"error": { "type": "err_not_found", "description": "the resource does not exist" }
}
…while bulk sends, the key endpoints, and every authentication failure return an error.code:
{
"status": "error",
"message": "missing authentication",
"error": { "code": "UNAUTHORIZED", "description": "missing authentication" }
}
| Status | Meaning |
|---|---|
400 | Malformed body, invalid id, or a from that is not an approved sender ID. Validation failures list fields in error.validation_errors. |
401 | Missing, malformed, or revoked API key. |
404 | No such resource on your account. |
422 | insufficient balance — your prepaid balance does not cover the send (documented on the bulk operation). |
Sender IDs
The from on a message is a sender ID — the name the recipient sees. Sender IDs are
approved before use, so you cannot invent one at send time; an unapproved value is rejected
with 400 unknown or unapproved sender id. Yours are captured at registration and approved on
the network-operator side, usually in about 30 minutes. Manage them in the dashboard, or list
them over the API with Sender IDs.
Billing and segments
Accounts are prepaid. Each send places a hold on your balance, and a send that your balance will not cover is rejected outright — never half-delivered. Check what you hold with Balances.
SMS is billed per segment, not per message:
| Encoding | When | Single segment | Per segment when concatenated |
|---|---|---|---|
| GSM-7 | Every character is in the GSM-7 alphabet | 160 chars | 153 chars |
| UCS-2 | Any character outside GSM-7 (an emoji, a curly quote, an accent) | 70 chars | 67 chars |
One stray character switches the whole message to UCS-2 and can more than double its cost.
The API returns the amount held as charge, in ten-thousandths of charge_currency
(10000 == 1.00, so 450 == 0.045). Note the type differs by endpoint: POST /api/v1/sms
returns charge as a string ("450"), while POST /api/v1/sms/bulk returns it as an
integer (450). Coerce on the way in.
Knowing whether it arrived
A 202 means Sendai accepted and queued your message — not that it arrived. Two fields track
the journey:
status—created→enqueued→processing→success, orfailed.delivered_at—nulluntil the carrier confirms delivery, then a timestamp.
status: success means the send pipeline completed. Confirmed handset delivery is
delivered_at being non-null — that is the only delivery signal the API exposes. Carrier
confirmation is asynchronous and can take seconds to hours, so poll
GET /api/v1/sms/{id}, or register a webhook and let
Sendai call you.
The resources
The reference is organised by resource — one page each, with the object first and one section per operation:
| Operation | Call |
|---|---|
| Send an SMS | POST /api/v1/sms |
| Retrieve an SMS | GET /api/v1/sms/{id} |
| Send bulk SMS | POST /api/v1/sms/bulk |
| API tokens | POST / GET / DELETE /api/v1/api-tokens |
| Balances | GET /api/v1/accounts/{id}/balances |
| Sender IDs | GET /api/v1/accounts/{id}/channel-identities |
Prefer a machine-readable contract? Download the OpenAPI document — the same operations, ready for codegen and tooling.