Quickstart
Send your first SMS in three steps.
- 1Sign in and create an API key at /api-keys.
- 2Get one of your sender IDs approved.
- 3Call
POST /api/smswith your message.
$ curl -X POST https://bulksms.oasistech.co.tz/api/sms \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": "+255712345678", "sender": "ACME", "message": "Habari Mohammed, asante kwa kununua nasi." }'
Authentication
Every request needs a Bearer token. Create one in the dashboard under API keys.
Authorization: Bearer YOUR_API_KEY
- • Tokens are scoped per tenant. They never expire unless you set a TTL.
- • Optionally restrict each key to specific IPs or CIDR ranges in the dashboard.
- • Lost a key? Revoke it and create a new one — keys aren't re-shown after creation.
Base URL & versioning
All endpoints accept JSON and return JSON.
https://bulksms.oasistech.co.tz
https://bulksms.oasistech.co.tz/v1
Use /v1/sms in production. Responses to /v1 URLs include a Sunset header when an endpoint is retired.
/api/sms
Send SMS
Send one or many messages in a single call. Per-recipient personalisation is supported via params.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| to | string | string[] | yes | One phone or an array. E.164 (e.g. +255712345678). |
| sender | string | yes | An approved sender ID for your tenant. 3–11 characters. |
| message | string | yes | Up to 1600 chars. Supports {placeholder}. |
| params | object | no | Replaces {key} placeholders in the message body. |
| idempotency_key | string | no | Can also be sent as an Idempotency-Key header. |
| scheduled_at | string | no | ISO-8601 datetime, 30 sec – 90 days from now. Credits reserved, response is 202, returns scheduled_id. Cancellable from the dashboard. |
Single recipient
curl -X POST https://bulksms.oasistech.co.tz/api/sms \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": "+255712345678", "sender": "ACME", "message": "Your verification code is 492715." }'
Batch + personalisation
{
"to": [
{"number": "+255712345678", "params": {"name":"Amina"}},
{"number": "+255754901234", "params": {"name":"Juma"}}
],
"sender": "ACME",
"message": "Habari {name}, asante kwa kununua nasi."
}
Response — 200 OK
{
"status": "ok",
"results": [
{ "to": "+255712345678", "status": "sent",
"credits_used": 1, "segments": 1,
"provider_ref": "12345678" }
],
"credits_used": 1,
"balance_after": 142316
}
/api/balance
Check balance
Returns the current credit balance for the authenticated tenant.
curl https://bulksms.oasistech.co.tz/api/balance \
-H "Authorization: Bearer YOUR_API_KEY"
{
"credits": 142316
}
/api/logs
List messages
Paginated list of your sent SMS, newest first.
Query parameters
| Param | Default | Notes |
|---|---|---|
| type | sms | Fixed. |
| page | 1 | 1-indexed. |
| limit | 50 | 1–100. |
| search | — | Matches recipient, sender, or message body. |
| status | — | sent, failed, pending, delivered, undelivered. |
| from | — | YYYY-MM-DD |
| to | — | YYYY-MM-DD |
curl "https://bulksms.oasistech.co.tz/api/logs?status=failed&from=2026-05-01" \ -H "Authorization: Bearer YOUR_API_KEY"
{
"data": [
{
"id": 84211,
"recipient": "+255712345678",
"sender": "ACME",
"message": "Your code is 492715.",
"status": "sent",
"credits_used": 1,
"segments": 1,
"encoding": "gsm",
"provider_ref": "12345678",
"created_at": "2026-05-17T07:08:50Z"
}
],
"page": 1, "limit": 50,
"total": 1, "total_pages": 1
}
/api/sender-ids
List sender IDs
Returns your sender IDs and their current approval status.
curl "https://bulksms.oasistech.co.tz/api/sender-ids?status=approved" \ -H "Authorization: Bearer YOUR_API_KEY"
{
"sender_ids": [
{ "id": 12, "sender": "ACME", "country": "TZ",
"status": "approved", "reviewed_at": "2026-05-10T14:32:00Z" }
],
"total": 1
}
Requesting a new sender ID is a one-time review and is done from the dashboard, not the API.
Webhooks
Get notified the moment a message is delivered, fails, or a recipient opts out. Add and manage webhook endpoints from the dashboard under Webhooks.
Event types
sms.deliveredCarrier confirmed delivery.sms.failedSend failed (invalid number, blocked, etc).sms.expiredCarrier didn't confirm before TTL.credits.lowBalance dropped below your alert threshold.recipient.opted_outRecipient replied STOP.
Example payload
{
"event": "sms.delivered",
"id": 84211,
"recipient": "+255712345678",
"sender": "ACME",
"provider_ref": "12345678",
"delivered_at": "2026-05-17T07:08:51Z"
}
Verify the signature
Every delivery includes an X-BulkSMS-Signature header. It is an HMAC-SHA256 over the raw request body, using the secret shown once when you created the webhook.
// Node.js const crypto = require('crypto'); app.post('/bulksms-webhook', (req, res) => { const sig = req.headers['x-bulksms-signature']; const expected = crypto.createHmac('sha256', WEBHOOK_SECRET) .update(req.rawBody) .digest('hex'); if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) { return res.status(401).end(); } // process req.body… res.status(200).end(); });
// PHP $sig = $_SERVER['HTTP_X_BULKSMS_SIGNATURE'] ?? ''; $raw = file_get_contents('php://input'); $expected = hash_hmac('sha256', $raw, $WEBHOOK_SECRET); if (!hash_equals($expected, $sig)) http_response_code(401);
2xx as soon as you've persisted the event — do the rest of the work asynchronously.
Errors
All errors return JSON with an error string and an HTTP status code.
| Status | Meaning | Typical cause |
|---|---|---|
| 400 | Bad Request | Missing or malformed field. |
| 401 | Unauthorized | Missing or invalid bearer token. |
| 402 | Payment Required | Not enough credits. Top up. |
| 403 | Forbidden | Sender ID not approved · KYC not approved · token scope missing · IP not allowlisted. |
| 404 | Not Found | Unknown endpoint or method. |
| 409 | Conflict | Idempotency key already processed. |
| 429 | Too Many Requests | Rate limited. Check Retry-After. |
| 502 | Bad Gateway | Upstream carrier rejected the message. Detail in body. |
| 503 | Service Unavailable | Platform provider not configured or paused. |
Rate limits
Limits are applied per API key per minute.
/api/sms60 requests / minute (batches OK — counted per request, not per recipient)./api/balance·/api/logs60 / minute.
429 responses include a Retry-After header (seconds). Back off, then retry.
Idempotency
Send the same request twice and only one SMS goes out.
Idempotency-Key: order-1023-confirmation
Keys are scoped per tenant. A retry with the same key returns 409 if the original was already charged. Use a key per logical operation (an order ID, a verification attempt), not per HTTP retry.
Message segments
You're billed per segment per recipient.
| Encoding | 1 segment ≤ | Multi-segment chunk |
|---|---|---|
| GSM-7 (English) | 160 | 153 |
| Unicode (Swahili diacritics, emoji) | 70 | 63 |
A single non-GSM character switches the whole message to Unicode encoding. The response tells you exactly how many segments and credits the send used — trust that, not your own count.