Build reliable messaging workflows with a clear, live reference.
Authentication, request examples, permissions, aliases, response behaviour and error handling are arranged so developers can move from first call to production without hunting through scattered notes.
Introduction
SwalaSMS Developer API lets your system send and track SMS messages through the same routing, sender ID, wallet, spam-control, and country-credit rules used by the web portal.
Use a sandbox API key while testing. Sandbox keys return realistic queued/delivered responses but never send real SMS or consume SMS credits. Use a live key only after your sender IDs, SMS route, wallet balance, and provider credits are ready.
Every request must include Authorization: Bearer YOUR_API_KEY. Send endpoints also support Idempotency-Key; pass a stable unique value from your own system when retrying a request so the same SMS is not queued twice.
Phone numbers should be sent in E.164 format, for example +255712345678. A successful send returns 202 Accepted because delivery is queued and processed asynchronously. Use the status endpoint to check the current state of a message.
Common statuses are queued, processing, sent, delivered, failed, and scheduled. Validation, sender ID, route, wallet, provider-credit, spam-policy, and recipient opt-out errors return JSON with success: false and a readable message.
Every request carries the key issued with your application as a bearer token. A key is shown once, when it is created. The base URL is:
https://swalasms.co.tz/api/v1
Authorization: Bearer swala_live_xxx
A key carries only the permissions granted to its application. Each endpoint below
names the one it needs; a key without it is answered with 403.
Messaging
Send messages and check delivery and balance.
Check SMS balance
Also answers at
https://swalasms.co.tz/api/v1/sms/balance — identical behaviour.
Return the customer SMS credit balance for the API key owner.
The top-level balance is the total available SMS units across all enabled country wallets. The countries array breaks the balance down per country with wallet UID, ISO country code, calling code, and remaining SMS units.
Use this endpoint before sending if your application needs to warn users before a campaign is queued. The actual send endpoints still perform the final wallet and provider-credit checks inside a database transaction.
Example
curl -X GET https://swalasms.co.tz/api/v1/balance \ -H "Authorization: Bearer swala_live_xxx"
Responses
{
"success": true,
"request_uid": "9f2c1e3a-4b7d-4e2b-9c3e-2f6a7d8b1c4e",
"data": {
"balance": "value",
"currency": "SMS",
"countries": [
{
"wallet_uid": "9f2c1e3a-4b7d-4e2b-9c3e-2f6a7d8b1c4e",
"country": "TZ",
"calling_code": "+255",
"balance": "value"
}
]
}
}
Send a single SMS
Also answers at
https://swalasms.co.tz/api/v1/sms/quick-message — identical behaviour.
Queue one SMS for delivery through the active SwalaSMS routing engine, under the same rules a message gets when it is composed in the portal. Nothing is relaxed because the request arrived over the API.
The API key owner must have an active account, an approved sender ID assigned to the account, a verified SMS route for the destination country, enough customer SMS credit, and an available provider route with provider credit. The message body is checked by the spam policy before it is accepted.
Send recipient in E.164 format, for example +255712345678. Include Idempotency-Key when retrying from your application; if the same key was already accepted, SwalaSMS returns the existing message instead of creating a duplicate.
Successful requests return 202 Accepted because the SMS is queued and delivered asynchronously. The response carries the message uid for your own records; delivery outcomes arrive at your configured webhook.
For one message to many recipients, use POST /api/v1/sms/send-bulk. Repeating the same body and sender through this single-message endpoint is rate-limited.
Body
| Field | Type | Notes |
|---|---|---|
recipient
*
|
string | max 32 characters |
sender_id
*
|
string | max 32 characters |
body
*
|
string | max 1600 characters |
Example
curl -X POST https://swalasms.co.tz/api/v1/sms/messages \
-H "Authorization: Bearer swala_live_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: your-unique-reference" \
-d '{
"recipient": "+255712345678",
"sender_id": "YOURBRAND",
"body": "Your order is ready for pickup."
}'
Responses
{}
{
"message": "A human-readable description of the result.",
"errors": {
"recipients.0": [
"The field is invalid."
]
}
}
List messages and their delivery status
Every message this account has sent, newest first, with what became of it.
A send returns queued: the carrier decides delivery seconds or minutes later, so this is how an integration finds out. Poll it, or read one message directly with GET /sms/messages/{uid} using the uid the send returned.
Filter with status (queued, sent, delivered, failed), channel (api or web, to separate your integration's traffic from messages staff sent in the portal), and from / to as dates. per_page defaults to 50 and is capped at 100.
Each entry carries status, queued_at, sent_at, delivered_at, failure_reason and the billed units.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
channel
|
query | string | |
status
|
query | string | |
from
|
query | string | |
to
|
query | string | |
per_page
|
query | integer |
Example
curl -X GET https://swalasms.co.tz/api/v1/sms/messages \ -H "Authorization: Bearer swala_live_xxx"
Responses
{
"success": true,
"request_uid": "9f2c1e3a-4b7d-4e2b-9c3e-2f6a7d8b1c4e",
"data": [
{
"uid": "9f2c1e3a-4b7d-4e2b-9c3e-2f6a7d8b1c4e",
"recipient": "+255712345678",
"sender_id": "SWALASMS",
"units": 1,
"channel": "api",
"status": "delivered",
"provider_message_id": "prov_abc123",
"queued_at": "2026-08-25T09:00:00+00:00",
"sent_at": "2026-08-25T09:00:02+00:00",
"delivered_at": "2026-08-25T09:00:07+00:00",
"failure_reason": null
}
],
"meta": {
"current_page": 1,
"per_page": 20,
"total": 42,
"last_page": 3
}
}
Send bulk SMS
Queue the same SMS body to many recipients in one API call.
Use this endpoint when your application already has the recipient list and each number is ready in E.164 format. The request accepts up to 1000 recipients. SwalaSMS still creates one local message per recipient so balance, reports, idempotency and delivery webhooks keep working the same way as the portal.
The payload mirrors provider bulk APIs: send recipients, message, sender_id, optional campaign_name, and optional scheduled_at.
Successful requests return campaign-level counts. A recipient can still be skipped if its route, wallet, sender ID, provider credit or spam checks fail.
Body
| Field | Type | Notes |
|---|---|---|
recipients
*
|
array | One to 1000 recipient phone numbers. · max 1000 items |
message
*
|
string | The same SMS body sent to every recipient. · max 1600 characters |
sender_id
*
|
string | Your approved SwalaSMS sender ID name. · max 32 characters |
campaign_name
optional
|
string | A name for this campaign in reports. · max 120 characters |
scheduled_at
optional
|
string | Future ISO-8601 date/time, or omit/null to send now. |
Example
curl -X POST https://swalasms.co.tz/api/v1/sms/send-bulk \
-H "Authorization: Bearer swala_live_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: your-unique-reference" \
-d '{
"recipients": [
"+255755000001",
"+255755000002",
"+255755000003"
],
"message": "Flash sale! 30% off today only.",
"sender_id": "YOURBRAND",
"campaign_name": "June Flash Sale",
"scheduled_at": null
}'
Responses
"value"
{
"success": true,
"request_uid": "9f2c1e3a-4b7d-4e2b-9c3e-2f6a7d8b1c4e",
"mode": "sandbox",
"data": {
"uid": "4c3b2a19-5566-7788-99aa-bbccddeeff00",
"name": "August Promo",
"status": "queued",
"recipient_count": 3,
"queued_count": 3,
"failed_count": 0,
"scheduled_at": null,
"failures": [],
"results": [
{
"recipient": "+255755000001",
"status": "queued",
"message_uid": "9f2c1e3a-4b7d-4e2b-9c3e-2f6a7d8b1c4e"
}
]
}
}
{
"message": "A human-readable description of the result.",
"errors": {
"recipients.0": [
"The field is invalid."
]
}
}
Check one message
The delivery status of a single message, by the uid the send returned.
status moves queued → sent → delivered, or ends at failed with a failure_reason written for a person. delivered_at is set the moment the carrier confirms it. The response carries the queued, sent, delivered and failure timestamps when they are known; which provider carried the message is not exposed.
An invalid UID is answered with a validation error, and a valid one belonging to another account with 404 — the two are told apart deliberately, so a wrong id is a mistake to fix rather than a hint about somebody else's traffic.
A sandbox key answers delivered immediately without looking anything up, so an integration can be built end to end before a real message is ever sent.
Parameters
| Name | In | Type | Notes |
|---|---|---|---|
uid
* |
path | string |
Example
curl -X GET https://swalasms.co.tz/api/v1/sms/messages/{uid} \
-H "Authorization: Bearer swala_live_xxx"
Responses
{
"success": true,
"request_uid": "9f2c1e3a-4b7d-4e2b-9c3e-2f6a7d8b1c4e",
"data": {
"uid": "9f2c1e3a-4b7d-4e2b-9c3e-2f6a7d8b1c4e",
"recipient": "+255712345678",
"sender_id": "SWALASMS",
"units": 1,
"channel": "api",
"status": "delivered",
"provider_message_id": "prov_abc123",
"queued_at": "2026-08-25T09:00:00+00:00",
"sent_at": "2026-08-25T09:00:02+00:00",
"delivered_at": "2026-08-25T09:00:07+00:00",
"failure_reason": null,
"campaign_uid": "4c3b2a19-5566-7788-99aa-bbccddeeff00",
"campaign_name": "August Promo",
"failure_context": null,
"delivery_report": {
"checked_at": "2026-08-25T09:05:00+00:00",
"status": "delivered",
"delivery_status": "DELIVRD"
}
}
}
{
"success": true,
"request_uid": "9f2c1e3a-4b7d-4e2b-9c3e-2f6a7d8b1c4e",
"message": "SMS message was not found for this API key."
}
{
"success": true,
"request_uid": "9f2c1e3a-4b7d-4e2b-9c3e-2f6a7d8b1c4e",
"message": "Message UID must be a valid UUID."
}
Sender IDs
Request a sender ID and track its approval. Mirrors the web flow exactly.
List sender IDs
Also answers at
https://swalasms.co.tz/api/v1/sms/sender-ids — identical behaviour.
Every sender ID this account can send with, plus every request it has made, at any status.
Use this to poll a request submitted with POST /sender-ids. A sender ID may only be used for sending once is_usable is true (status is approved). When a request was turned down, rejection_reason explains why.
Statuses: pending, approved, rejected, suspended.
Example
curl -X GET https://swalasms.co.tz/api/v1/sender-ids \ -H "Authorization: Bearer swala_live_xxx"
Responses
{
"success": true,
"request_uid": "9f2c1e3a-4b7d-4e2b-9c3e-2f6a7d8b1c4e",
"data": [
{
"uid": "a1b2c3d4-1111-2222-3333-444455556666",
"sender_id": "SWALASMS",
"country_uid": "b2c3d4e5-2222-3333-4444-555566667777",
"country_code": "+255",
"country_name": "Tanzania",
"status": "approved",
"is_default": true,
"is_global": false,
"sample_message": "Your order has shipped and is on its way.",
"is_usable": true,
"submitted_document_uids": [
"c3d4e5f6-3333-4444-5555-666677778888"
],
"requested_at": "2026-08-01T10:00:00+00:00",
"updated_at": "2026-08-02T08:00:00+00:00"
}
]
}
Request a sender ID
Also answers at
https://swalasms.co.tz/api/v1/sms/sender-ids — identical behaviour.
Submits a sender ID for manual review. Applies exactly the same gates as the web form, so anything refused here would have been refused there.
Attaching documents. Every requirement from GET /sender-ids/requirements with is_required: true must be present, either way:
- JSON:
documents_base64: [{ "requirement_uid": "…", "filename": "certificate.pdf", "content_base64": "…" }] - Multipart: a file part named
documents[<requirement_uid>]
Accepted formats are pdf, jpg, jpeg and png, up to 5 MB each.
Errors.
| Status | error |
Meaning |
|---|---|---|
| 422 | sender_id_not_eligible |
Not enough SMS credits purchased yet. |
| 422 | route_not_verified |
No verified SMS route for that country. |
| 422 | country_not_available |
That country is not open on this platform. |
| 409 | sender_id_already_requested |
This name already exists for this country. Poll GET /sender-ids instead; retrying never succeeds. |
Approval is manual. Poll GET /sender-ids until is_usable is true.
Body
| Field | Type | Notes |
|---|---|---|
sender_id
*
|
string | |
country_uid
*
|
string | |
sample_message
*
|
string | max 500 characters |
purpose
*
|
string | |
documents
optional
|
array or null | |
documents_base64
optional
|
array or null |
Example
curl -X POST https://swalasms.co.tz/api/v1/sender-ids \
-H "Authorization: Bearer swala_live_xxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: your-unique-reference" \
-d '{
"sender_id": "YOURBRAND",
"country_uid": "TZ",
"sample_message": "Your order is ready for pickup.",
"purpose": "value",
"documents": "value",
"documents_base64": "value"
}'
Responses
{
"success": true,
"request_uid": "9f2c1e3a-4b7d-4e2b-9c3e-2f6a7d8b1c4e",
"message": "A human-readable description of the result.",
"data": {
"uid": "a1b2c3d4-1111-2222-3333-444455556666",
"sender_id": "SWALASMS",
"country_uid": "b2c3d4e5-2222-3333-4444-555566667777",
"country_code": "+255",
"country_name": "Tanzania",
"status": "approved",
"is_default": true,
"is_global": false,
"sample_message": "Your order has shipped and is on its way.",
"is_usable": true,
"submitted_document_uids": [
"c3d4e5f6-3333-4444-5555-666677778888"
],
"requested_at": "2026-08-01T10:00:00+00:00",
"updated_at": "2026-08-02T08:00:00+00:00"
}
}
{
"success": true,
"request_uid": "9f2c1e3a-4b7d-4e2b-9c3e-2f6a7d8b1c4e",
"error": "sender_id_already_requested",
"message": "A human-readable description of the result."
}
{
"message": "A human-readable description of the result.",
"errors": {
"recipients.0": [
"The field is invalid."
]
}
}
Get sender ID requirements
Also answers at
https://swalasms.co.tz/api/v1/sms/sender-ids/requirements — identical behaviour.
Call this before requesting a sender ID. It returns everything that decides whether a request will be accepted, and those rules differ per platform and per account.
eligibility.can_request—falsemeans the account has not bought enough SMS credits yet.required_unitsandminimum_purchase_amountsay how much is needed. Do not attempt the request.countries[]— the only countries this account may request for. A country appears here once a phone number has been verified for it under SMS Routes, because a sender ID is registered per country.documents[]— every entry withis_required: truemust be attached when you request. Use itsuid.sender_id_rules— 3 to 11 characters of letters, numbers and single spaces between words, and a list of reserved words that are always refused. Case is yours to choose and is carried through to the handset.purposes— the allowed purpose values; send exactly one aspurpose.sample_message_rules— 50 to 500 characters.
Example
curl -X GET https://swalasms.co.tz/api/v1/sender-ids/requirements \ -H "Authorization: Bearer swala_live_xxx"
Responses
{
"success": true,
"request_uid": "9f2c1e3a-4b7d-4e2b-9c3e-2f6a7d8b1c4e",
"data": {
"eligibility": {
"can_request": true,
"purchased_units": 1,
"required_units": 50,
"currency": "SMS",
"minimum_purchase_amount": "2.50",
"price_per_unit": "2.50",
"reason": "The request could not be completed."
},
"countries": [
{}
],
"documents": [
{
"uid": "9f2c1e3a-4b7d-4e2b-9c3e-2f6a7d8b1c4e",
"name": "Jane Doe",
"is_required": true,
"accepted_formats": [
"pdf",
"jpg",
"jpeg",
"png"
],
"max_size_kb": 5120
}
],
"purposes": [
"Notification",
"Transactional",
"OTP / Verification",
"Promotional",
"Marketing",
"Alerts & Reminders",
"Other"
],
"sender_id_rules": {
"min_length": 3,
"max_length": 11,
"pattern": "^[A-Za-z0-9]+( [A-Za-z0-9]+)*$",
"description": "Letters, numbers, and single spaces between words. No symbols or accents. Outer whitespace is trimmed, repeated spaces are collapsed, and the sender ID is stored in uppercase.",
"reserved_names": [
"ADMIN",
"ALERT",
"BANK",
"BANKING",
"GOV",
"GOVT",
"INFO",
"LOGIN",
"NOTICE",
"OTP",
"PASSWORD",
"SECURE",
"SECURITY",
"SERVICE",
"SUPPORT",
"SYSTEM",
"TAX",
"UNKNOWN",
"UNVERIFIED",
"VERIFY",
"WHATSAPP"
]
},
"sample_message_rules": {
"min_length": 50,
"max_length": 500,
"description": "A real example of the messages you will send with this sender ID. Reviewers read it."
}
}
}
Account
Who the key belongs to, which routes are verified, and what is left to spend.
Read the account profile
Returns the account, its verified SMS routes and the credit balance held for each country.
Example
curl -X GET https://swalasms.co.tz/api/v1/sms/profile \ -H "Authorization: Bearer swala_live_xxx"
Responses
{
"success": true,
"request_uid": "9f2c1e3a-4b7d-4e2b-9c3e-2f6a7d8b1c4e",
"data": {
"uid": "9f2c1e3a-4b7d-4e2b-9c3e-2f6a7d8b1c4e",
"name": "Jane Doe",
"email": "jane@example.com",
"phone": "+255712345678",
"phone_verified_at": "2026-08-25T09:00:00+00:00",
"country": {
"uid": "9f2c1e3a-4b7d-4e2b-9c3e-2f6a7d8b1c4e",
"iso2": "TZ",
"calling_code": "+255",
"name": "Jane Doe"
},
"balances": [
{
"wallet_uid": "9f2c1e3a-4b7d-4e2b-9c3e-2f6a7d8b1c4e",
"country_uid": "9f2c1e3a-4b7d-4e2b-9c3e-2f6a7d8b1c4e",
"country": "TZ",
"calling_code": "+255",
"balance": 500,
"currency": "SMS"
}
],
"routes": "value"
}
}
Every failure answers with JSON carrying success: false, the
request_uid for the attempt, and a message written for a person.
Quote the request_uid when contacting support.
| Status | Meaning | What to do |
|---|---|---|
| 401 | Key missing, expired or revoked | Issue a new key for the application |
| 403 | Key lacks the permission the endpoint needs | Grant the scope in application settings |
| 422 | Understood but refused — no credit, unapproved sender ID, unverified route, spam policy | Read message; it names the cause |
| 429 | Too many requests | Back off and retry; the limit is per key per minute |