Zino SMS API

The Zino SMS REST API lets your software send SMS, check delivery and read your balance. It speaks JSON over HTTPS. Base URL: https://YOUR-DOMAIN/api/v1

Authentication

Create an API key in your dashboard under API keys. Send it as a Bearer token on every request. Keys are shown once, so store them in your server's private config, never in browser code.

Authorization: Bearer zsk_live_xxxxxxxxxxxxxxxx

Sandbox testing

While your account is connected to the sandbox provider, no real SMS leaves our servers. Use these numbers to test every outcome:

Number ends withResult
any other digitsAccepted, then delivered a few seconds later
1111Accepted, then reported failed (handset unreachable)
0000Rejected immediately (invalid destination)

Send a message

POST/v1/messages

FieldTypeDescription
tostringRecipient. Local (0772123456) or international (+256772123456).
messagestringText to send.
fromstring, optionalAn approved sender ID. Defaults to the shared sender.
client_refstring, optionalYour own reference. Sending the same ref to the same number twice returns the first message instead of sending again.
schedule_atISO 8601, optionalSend later, e.g. 2026-12-01T09:00:00+03:00.
curl -X POST https://YOUR-DOMAIN/api/v1/messages \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to":"0772123456","message":"Hello from Zino SMS","client_ref":"order-1042"}'

Response 201:

{
  "ok": true,
  "message": {
    "id": "msg_ad1a650a249e0b1d", "to": "+256772123456", "from": "ZinoSMS",
    "status": "sent", "segments": 1, "credits": 1, "encoding": "GSM-7",
    "client_ref": "order-1042", "error": null, "created_at": "2026-11-01 14:43:04"
  },
  "balance": 2839
}

Statuses: queued (waiting or scheduled), sent (accepted by the carrier network), delivered, failed (credits refunded), cancelled.

Bulk send

POST/v1/messages/bulk   up to 1,000 messages per request. Credits for the whole request are checked first, so it either fits your balance or returns 402.

# Same text to many numbers
{ "to": ["0772111111", "0772222222"], "message": "Shop closed Monday", "from": "MyShop" }

# Different text per number
{ "messages": [ {"to": "0772111111", "message": "Hi Amina, your order is ready", "client_ref": "o-1"},
                {"to": "0772222222", "message": "Hi Brian, your order is ready", "client_ref": "o-2"} ] }

Invalid numbers are skipped and listed under rejected; you are only charged for accepted messages.

Message status

GET/v1/messages/{id}   accepts the message id or your client_ref.

List messages

GET/v1/messages?status=delivered&page=1&per=50&since=2026-11-01

Cost quote

POST/v1/quote   body {"to": "...", "message": "..."} returns encoding, segments and credits without sending.

Balance

GET/v1/balance

{ "ok": true, "credits": 2839, "low_balance_threshold": 50, "plan": "Business", "subscription": "active", "renews_at": "2026-11-22 13:51:47" }

Sender IDs

GET/v1/sender-ids   lists your IDs and their approval status. Request new ones in the dashboard.

Webhooks

Add an endpoint in the dashboard under Webhooks. We send a POST with JSON whenever a message finishes. Events: message.delivered, message.failed.

{ "event": "message.delivered", "created_at": "2026-11-01T14:43:09+00:00",
  "data": { "id": "msg_ad1a650a249e0b1d", "to": "+256772123456", "status": "delivered", "client_ref": "order-1042" } }

Verify the signature header X-ZinoSMS-Signature: sha256=<hex>, which is the HMAC-SHA256 of the raw request body using your endpoint secret. Reply with any 2xx status. Endpoints that keep failing are disabled after 20 consecutive failures.

$expected = 'sha256=' . hash_hmac('sha256', file_get_contents('php://input'), $secret);
if (!hash_equals($expected, $_SERVER['HTTP_X_ZINOSMS_SIGNATURE'] ?? '')) { http_response_code(401); exit; }

Errors

Errors return a non-2xx status and {"ok": false, "error": "code", "message": "..."}.

HTTPerrorMeaning
401missing_api_key / invalid_api_keyKey missing, wrong or revoked
402insufficient_creditsTop up your wallet
403sender_not_approved / account_suspendedSender ID not approved, or account suspended
404not_foundUnknown message or endpoint
422missing_field / invalid_number / invalid_senderFix the request
429rate_limitedSlow down, see Retry-After
500server_errorTry again shortly

Rate limits

Each key is limited per minute according to your plan (30 on Free, up to 1,000 on Enterprise). Every response includes X-RateLimit-Limit. When exceeded you receive 429 with a Retry-After header.

Segments and credits

Messages using the standard GSM-7 alphabet fit 160 characters in one segment, 153 per segment when longer. Messages with emoji or special letters use Unicode: 70 characters, then 67 per segment. Each segment costs the destination's credit rate (1 credit for Uganda). Failed messages are refunded.

Eduscape / partner API

For software platforms such as Eduscape School System. A partner key manages a separate wallet and API key per school, and can send on a school's behalf. Create partner keys in the dashboard (Partner accounts only).

Create a school wallet

POST/v1/partner/schools

{ "code": "school-123", "name": "Example Secondary School", "credits": 500 }
# credits is optional and is moved from your partner wallet. The response contains the school's own api_key, shown once.

Top up or reclaim school credits

POST/v1/partner/schools/{code}/credits   body {"credits": 200}. Negative numbers reclaim credits back to the partner wallet.

Other school calls

GET/v1/partner/schools   GET/v1/partner/schools/{code}   POST/v1/partner/schools/{code}/keys (rotate the school key)

Send for a school

POST/v1/partner/send

{ "school_code": "school-123",
  "type": "fees",                       # report_card | fees | notice  (used for reporting)
  "from": "Eduscape",
  "messages": [ { "to": "0772123456", "message": "Fees balance reminder for Term 1", "client_ref": "fee-2026-t1-0042" } ] }

The school's wallet is charged. Responses include school_balance. Delivery webhooks registered on the partner account include a school_code field in data. A school can also use its own key with the normal /v1/messages endpoints; schools may use their partner's approved sender IDs.