Rukia yaliyomo
Kwa watengenezaji

Eroyal Africa API

Eroyal Africa is a bulk SMS platform for businesses in Tanzania. The API lets you send transactional and marketing SMS at scale, verify users with one-time passcodes, and track delivery, through a small set of JSON HTTP endpoints.

Overview

This page covers the developer-facing API. Send SMS, list what you have sent, send and verify one-time passcodes, and receive delivery updates. Buying credit, sender names and billing happen in your Eroyal Africa account.

What you can build

  • Bulk and single SMS — send one message or thousands in a single request, immediately or scheduled for a future date.
  • Your own sender name — send under your approved brand name instead of a shared system sender.
  • OTP verification — generate and verify one-time passcodes for login, signup or checkout, by SMS and optionally email.
  • Delivery tracking — see what you have sent, and receive status updates on your own server.

Quickstart

Once you have an API key, sending an SMS is one request:

Send an SMS
curl -X POST https://portal.eroyal.africa/api/v1/messages \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sender_name": "MYBRAND",
    "is_scheduled": false,
    "messages": [
      { "receiver": "0712345678", "content": "Hello Asha, your order is ready" }
    ]
  }'

Every response, success or failure, comes back in the same envelope, so you can write one response handler for the whole API.

Authentication

Every endpoint except POST /otps/verify requires an API key, sent as a standard Authorization header.

API keys

Generate a key from your Eroyal Africa account, not from the API: sign in to portal.eroyal.africa, open API and press New API Key. Give it a name so you can tell your keys apart — one per environment or integration. The raw key is shown once, and every key begins txf_:

The shape of a key
txf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Shown once, save it immediately. We store only a SHA-256 hash of your key, never the raw value, so there is no way to show it again. If you lose it, generate a new one and delete the old.
Several keys at once. Your account can hold any number of active keys. Generating a new one does not invalidate the others, and deleting one takes effect immediately — the very next request made with it fails.

Using a key

Send it in the Authorization header, as a bearer token, on any protected endpoint:

Authenticated request
curl https://portal.eroyal.africa/api/v1/messages \
  -H "Authorization: Bearer txf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Keys do not expire and are not tied to a session, so there is no logout for them.

Authentication errors

401 Unauthorized
{
  "success": false,
  "status_code": 401,
  "status_text": "Unauthorized",
  "message": "invalid api key",
  "error": "invalid_token"
}
StatuserrorMeaning
401missing_tokenNo Authorization header was sent.
401invalid_tokenThe key does not match any account, or it has been deleted.
503not_configuredThe server is not set up to issue or check keys yet.

Base URL

Every endpoint in this reference is relative to a single base URL:

Base URL
https://portal.eroyal.africa/api/v1

So sending an SMS means a request to https://portal.eroyal.africa/api/v1/messages. Throughout this reference, paths are written relative to that base, e.g. POST /messages.

All traffic is HTTPS. Plain HTTP is not supported in production.

Versioning

/api/v1 is the current and only stable version. Breaking changes will arrive under a new prefix rather than changing this one in place.

Request & Response Format

All request bodies are JSON. Send Content-Type: application/json on every request that carries a body, and authenticate as described above unless the endpoint is marked public.

Response envelope

Success
{
  "success": true,
  "status_code": 200,
  "status_text": "OK",
  "message": "messages have been fetched",
  "data": { }
}
Error
{
  "success": false,
  "status_code": 400,
  "status_text": "Bad Request",
  "message": "no receiver has been provided",
  "error": "validation_error"
}

success tells you which shape you got without inspecting the status code. On success, data holds the payload and error is omitted. On failure, error is a short machine-readable code you can branch on, and message is a human-readable description safe to log.

Pagination

ParamDefaultDescription
page1Page number.
limit10Records per page (max 1000).
sort_dirdescasc or desc, by creation time.
status—Filter by exact status.
Paginated response
{
  "success": true,
  "status_code": 200,
  "status_text": "OK",
  "message": "messages have been fetched",
  "data": {
    "data": [ { "id": "…", "status": "sending" } ],
    "total": 42,
    "page": 1,
    "limit": 10,
    "total_pages": 5,
    "has_next_page": true,
    "has_previous_page": false,
    "next_page": 2,
    "previous_page": null
  }
}
Data scoping. List endpoints only ever return records belonging to the account whose key made the request.

HTTP status codes

StatusStatus textMeaning
200OKRequest succeeded.
201CreatedMessages were queued.
400Bad RequestMissing or invalid fields — see error and message.
401UnauthorizedMissing or invalid API key.
404Not FoundThe route or the record does not exist.
405Method Not AllowedThe method is not supported on this path.
500Internal Server ErrorUnexpected server-side failure.

SMS API

Send single or bulk SMS and list what you have sent. One SMS unit is 160 characters; longer content is billed as ceil(length / 160) parts.

POST /messages

Creates and queues one message per recipient. If you pass a sender name it must be one approved for your account. Your balance is checked and deducted as the messages are queued.

FieldTypeRequiredDescription
messagesarrayrequiredOne or more { receiver, content } objects. Non-empty.
messages[].receiverstringrequiredRecipient phone number. Normalised to 255… international format.
messages[].contentstringrequiredMessage text. 160 characters is one billed unit.
sender_namestringoptionalAn approved sender name on your account. Omit to send under the system sender.
is_scheduledbooleanoptionalDefaults to false. When true, scheduled_date is required.
scheduled_datestring (ISO 8601)optionalWhen it falls due the message moves from scheduled to sending.

Bulk

Request body
{
  "sender_name": "MYBRAND",
  "is_scheduled": false,
  "messages": [
    { "receiver": "0711111111", "content": "Hello Asha, your order is ready" },
    { "receiver": "0722222222", "content": "Hello Juma, your order is ready" }
  ]
}

Scheduled

Request body
{
  "sender_name": "MYBRAND",
  "is_scheduled": true,
  "scheduled_date": "2026-12-20T08:00:00Z",
  "messages": [
    { "receiver": "0712345678", "content": "Reminder: your appointment is tomorrow" }
  ]
}

Response

The messages are queued, not returned inline. You get back the id of the batch, which you can look up afterwards.

201 Created
{
  "success": true,
  "status_code": 201,
  "status_text": "Created",
  "message": "messages have been created",
  "data": {
    "id": "6b2f6e2a4b0e4e9a9a2e1a2b3c4d5e6f",
    "recipients": 2,
    "count": 2,
    "status": "sending"
  }
}
Message lifecycle. A batch starts as sending, or scheduled until it is due. From there each message moves through processing → sent → delivered | undelivered | failed.

Errors

StatuserrorMeaning
400validation_errorNo messages, a missing receiver or empty content, or scheduled_date missing.
400creation_errorThe sender name is not approved for your account, or your balance is not enough.
401invalid_tokenMissing or invalid API key.

GET /messages

Paginated list of what this account has sent, newest first. Accepts page, limit, status and sort_dir.

Request
curl "https://portal.eroyal.africa/api/v1/messages?page=1&limit=20&sort_dir=desc" \
  -H "Authorization: Bearer $API_KEY"

GET /messages/{id}

One batch, by the id returned when you sent it.

200 OK
{
  "success": true,
  "status_code": 200,
  "status_text": "OK",
  "message": "message has been fetched",
  "data": {
    "id": "6b2f6e2a4b0e4e9a9a2e1a2b3c4d5e6f",
    "recipients": 2,
    "count": 2,
    "sender_name": "MYBRAND",
    "content": "Hello Asha, your order is ready",
    "is_scheduled": false,
    "scheduled_date": null,
    "status": "sending",
    "created_at": "2026-09-29T08:12:41+00:00"
  }
}
StatuserrorMeaning
404not_foundNo batch exists with that id on this account.

OTP API

Send one-time passcodes for login, signup or transaction confirmation, and verify them server-side. Codes are single-use and expire after 30 minutes.

POST /otps

Sends a 6-digit code by SMS, and by email as well if you give an address.

FieldTypeRequiredDescription
phone_numberstringrequiredRecipient number. Normalised to 255… format.
email_addressstringoptionalWhen set, the code is emailed as well.
sender_namestringoptionalAn approved sender name on your account.
brand_namestringoptionalThe brand written into the message. Defaults to your account's.
Request
curl -X POST https://portal.eroyal.africa/api/v1/otps \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "phone_number": "0712345678",
    "email_address": "customer@example.com",
    "brand_name": "My Brand"
  }'

POST /otps/verify Public

Consumes a code. Each code can be verified once. This endpoint needs no API key, so it can be called straight from a signup or login screen.

Request
curl -X POST https://portal.eroyal.africa/api/v1/otps/verify \
  -H "Content-Type: application/json" \
  -d '{ "code": 484895, "phone_number": "0712345678" }'
StatuserrorMeaning
400validation_errorcode was missing.
400verification_errorNo matching, unexpired code for that number or address.

Webhooks

Rather than polling, you can have delivery updates pushed to your own server. Tell us the URL and we POST to it when a message you sent lands or fails.

POST <your webhook url>
{
  "entity": "messages",
  "id": "6b2f6e2a-4b0e-4e9a-9a2e-1a2b3c4d5e6f",
  "status": "delivered"
}

Respond quickly with any 2xx status to acknowledge receipt. Treat a callback as a prompt to re-check rather than as proof on its own, and keep the endpoint fast and reachable.

Set the URL with us, not through the API. Webhook URLs are account configuration — email us the address you want and we will set it on your account.

Support

Stuck on an integration, a sender name or your balance? Reach the Eroyal Africa team:

Office

Kigamboni, Dar es Salaam, Tanzania
+255 765 492 700

Before you write

  • Check the error code in the response against the tables above — most integration problems are named there directly.
  • Have your account email to hand, and the batch id if it is about a send.
WhatsApp