Developer API · v2

Send messages from your own system

Connect your website, app, school or business system to NextSMS and send SMS, Mobile SMS and WhatsApp messages automatically. Every example on this page can be copied, and every request can be tried right here, safely, before you write any code.

Base URL https://messaging-service.co.tz

What is this API?

An API lets one computer system talk to another. Instead of logging in to the dashboard and typing a message, your system sends a short request to our address and we deliver the message for you. You can use it to:

Send automatically

Order confirmations, OTP codes, payment receipts, reminders.

Reach many people

One text to thousands of numbers, or a personal text for each.

Track delivery

Know which messages arrived, by report or instant callback.

Stay in control

Check your balance and test every request for free first.

How a request looks: every request goes to an address (URL) such as https://messaging-service.co.tz/api/sms/v2/text/single, carries your API token as a password, and sends the message details in JSON, a simple "name": "value" format. The answer also comes back in JSON.

Quick start (3 steps)

  1. 1

    Get your API token

    Log in to your dashboard and open Customer Profile (top-right menu) → Customization → API Keys. If the box is empty, click Generate Token. Copy it.

    Log in to get my token →
  2. 2

    Try it here, for free

    Paste the token in the box below, then press Try it on "Send a message". Test mode is on by default: nothing is sent or charged.

  3. 3

    Copy the code

    Pick your language (cURL, PHP, Python or JavaScript), replace YOUR_API_TOKEN and run it. Remove /test from the URL to send for real.

Your API token

Every request must prove it comes from you. Send your token in a header called Authorization:

Authorization: Bearer YOUR_API_TOKEN
Accept: application/json
Content-Type: application/json

If you think someone else has your token, generate a new one in Customer Profile (top-right menu) → Customization → API Keys: the old one stops working immediately.

Keep your token secret. Anyone who has it can send messages and spend your balance. Keep it on your server, never in a public website or mobile app.

Credentials for the "Try it" buttons

Requests go directly from this browser tab to https://messaging-service.co.tz. Anything you type here is never sent to this website and is forgotten when you close the tab.

Tip: log in and open this page from your dashboard (the API button at the top) to have your token filled in automatically.

Channels, numbers & scheduling

Channels

Send URLs contain a channel, for example /api/sms/v2/text/single. Pick the one you use:

ValueChannelWhat it means
smsInternet SMSNormal bulk SMS sent through the mobile networks with your approved Sender ID (the "from" name people see).
mobileMobile SMSSMS sent from your own phone SIM card through the NextSMS Android gateway app. "from" is ignored; the SIM number is the sender.
whatsappWhatsAppWhatsApp template messages sent from your connected WhatsApp Business account. Only the single send endpoint supports it.

Phone numbers

Write numbers as 255712345678: country code, then the number, without +, spaces or the leading 0.

Message length

One SMS holds 160 plain characters (70 with emojis or special letters). Longer texts use more SMS. Check with Count SMS parts.

Sending later

Add "date": "2026-12-01" and "time": "08:30" (East Africa Time). To repeat, also add repeat, start_date and end_date.

Account

GET /api/v2/balance Free

Check your balance

See how much credit is left on your account.

Returns your available balance. Call it before a big campaign to make sure you have enough credit.

Example request

curl -X GET 'https://messaging-service.co.tz/api/v2/balance' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'

Example response · 200 OK

{
    "sms_balance": 125000.5,
    "over_draft": 0,
    "type": "price",
    "default_balance": 6250,
    "default": "Internet Channel",
    "display": "125,000.5 TZS"
}
What does each field mean?
sms_balance
— For money-based accounts (type "price"): money left, overdraft included. For unit-based accounts (type "count"): SMS units left.
over_draft
— Extra credit your account manager allows you to use beyond your balance.
type
— "price" = your balance is money. "count" = your balance is SMS units.
default_balance
— About how many messages you can still send on your default channel.
default
— Your default channel: Internet Channel, Mobile Channel or Whatsapp Channel.
display
— Ready-to-show text, for example "125,000.5 TZS" or "1,234 SMS".

Send messages

POST /api/{channel}/v2/text/single Sends & charges

Send a message (SMS or Mobile SMS)

Send one text to one or many phone numbers, now or at a scheduled time.

The endpoint most people need. Put one number or a list of numbers in "to", your Sender ID in "from" and the message in "text". Every number gets the same text. To send different texts in one request, use "Send different messages in one request".

Fields

channel
string · in URL
Required
Where to send: sms, mobile or whatsapp. See "Channels".
from
string · in body
Required for sms
Your approved Sender ID, exactly as registered (capital letters matter). Ignored for mobile.
to
string|array · in body
Required
One phone number, or a list of numbers. Use 255XXXXXXXXX (no + sign). Local formats such as 0712345678 also work here. Repeated numbers are sent once.
text
string · in body
Required
The message. Long texts are split into parts (see "Count SMS parts").
reference
string · in body
Optional
Your own label for this send (up to 50 characters, no spaces). Use it later to find these messages in reports and logs.
flash
integer · in body
Optional
1 = flash SMS (pops up on the screen and is not saved in the inbox). 0 = normal SMS. Leave it out for normal SMS.
country
string · in body
Optional
Two-letter country code used to understand local number formats. Default TZ.
date
string · in body
Required with time
Send later on this date, format YYYY-MM-DD (East Africa Time). Must be today or later.
time
string · in body
Required with date
Send later at this time, 24-hour format HH:MM (East Africa Time, UTC+3).
repeat
string · in body
Required for repeating
Repeat the scheduled message: daily, weekly, monthly or yearly. Send it together with date, time, start_date and end_date.
start_date
string · in body
Required for repeating
First day of the repeat period, YYYY-MM-DD. Not before date.
end_date
string · in body
Required for repeating
Last day of the repeat period, YYYY-MM-DD. Not before start_date.
A 200 response means the request was processed. Check the status of every number in "messages": numbers that were rejected have status groupName "REJECTED" and no messageId.
Scheduled messages (with date and time) are accepted with status ACCEPTED and charged when they are sent.
If your balance runs out part way, the remaining numbers get FAILED_NOT_ENOUGH_CREDITS and the rest are still sent.

Example request

curl -X POST 'https://messaging-service.co.tz/api/sms/v2/text/single' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "from": "NEXTSMS",
    "to": [
        "255712345678",
        "255754000111"
    ],
    "text": "Hello! Your order 1001 is ready for pickup.",
    "reference": "order-1001"
}'

Example response · 200 OK

{
    "messages": [
        {
            "to": "255712345678",
            "status": {
                "groupId": 18,
                "groupName": "PENDING",
                "id": 51,
                "name": "ENROUTE (SENT)",
                "description": "Message sent to next instance"
            },
            "messageId": 7391827364512378881,
            "message": "Hello! Your order 1001 is ready for pickup.",
            "smsCount": 1,
            "price": 20
        }
    ]
}
What does each field mean?
to
— The phone number.
status.groupName
— PENDING = accepted, REJECTED = not sent (see status.name for why).
status.name / status.id
— The exact status. See "Statuses" below.
messageId
— The ID of this message. Save it to match delivery reports and callbacks. It is a very large number: store it as text.
message
— The text that was sent.
smsCount
— SMS parts used per recipient.
price
— Amount charged for this recipient (left out when zero).
Example of a rejected number
{
    "to": "25571234",
    "status": {
        "groupId": 19,
        "groupName": "REJECTED",
        "id": 69,
        "name": "REJECTED_INVALID_DESTINATION",
        "description": "Phone Number Not Valid, not supported country codes"
    },
    "sendReference": 7391827364512378880,
    "smsCount": 0,
    "sort": 1
}
POST /api/whatsapp/v2/text/single Sends & charges

Send a WhatsApp template message

Send an approved WhatsApp template to one or many numbers, with each person's own details.

WhatsApp business messages must use a template that Meta has approved. You choose the template by name, and give the values for its {placeholders} for each recipient, in the same order as "to".

Build the request for your own template

Every template needs a slightly different request. Log in and open this page from your dashboard: you can then pick one of your approved templates and get the exact request for it, ready to copy or try.

All 31 fields (reference)
account
string · in body
Required
The name of your active WhatsApp account, as shown under WhatsApp → Accounts.
template
string · in body
Required
The name of an active, approved template.
to
array · in body
Required
List of WhatsApp numbers as numbers (not text), for example [255712345678]. Every per-recipient list below follows this order.
personalisation
array · in body
Required if the template body has {placeholders}
One object per recipient, in "to" order. Keys are the body placeholders without braces, e.g. {"name": "Asha", "number": "123456"} for "Hi {name}, your order {number}…". No value may be empty.
header.image
object · in body
Required if the template header is an image
One image for every recipient. Use header.images instead to give each recipient their own image; do not send both.
header.image.file
string · in body
Required with header.image
A public https link to the image, or base64 data. PNG or JPEG, up to 3 MB.
header.image.name
string · in body
Optional
Optional file name for the image. Can be empty.
header.image.personalisation.qr_code_url
object · in body
Required if the template prints a QR code on the image
Draws a QR code on the image for each recipient, e.g. an event ticket. The QR code holds the template's QR link with {par} replaced by that recipient's value. All four fields below are required.
header.image.personalisation.qr_code_url.x_start
number · in body
Required with qr_code_url
Left edge of the QR code, as a percentage (0–100) of the image width.
header.image.personalisation.qr_code_url.y_start
number · in body
Required with qr_code_url
Top edge of the QR code, as a percentage (0–100) of the image height.
header.image.personalisation.qr_code_url.size
number · in body
Required with qr_code_url
Width and height of the QR code, as a percentage (0–100) of the image's shorter side. A QR code that would go past the edge is moved inside the image.
header.image.personalisation.qr_code_url.parameters
array · in body
Required with qr_code_url
The value put into the QR link for each recipient, in "to" order, e.g. their ticket number.
header.images
array · in body
Required for one image per recipient
A different image for each recipient, in "to" order: [{"file": "...", "name": ""}, ...]. Same file rules as header.image.file. The number of entries must equal the number of recipients.
header.document
object · in body
Required if the template header is a document
One PDF for every recipient. Use header.documents instead to give each recipient their own PDF; do not send both.
header.document.file
string · in body
Required with header.document
A public https link to the PDF, or base64 data. PDF only, up to 1 MB.
header.document.name
string · in body
Optional
The file name the recipient sees, e.g. "Invoice.pdf".
header.document.caption
string · in body
Optional
Optional text shown under the document.
header.documents
array · in body
Required for one document per recipient
A different PDF for each recipient, in "to" order: [{"file": "...", "name": "Invoice-Asha.pdf", "caption": ""}, ...]. Same rules as header.document. The number of entries must equal the number of recipients.
header.location
object · in body
Required if the template header is a location
A map pin shown at the top of the message. All four fields below are required.
header.location.latitude
string · in body
Required with header.location
Latitude in decimal degrees.
header.location.longitude
string · in body
Required with header.location
Longitude in decimal degrees.
header.location.name
string · in body
Required with header.location
Name of the place.
header.location.address
string · in body
Required with header.location
Address of the place.
header.text.personalisation.parameters
array · in body
Required if the header text has a placeholder
For a text header with a placeholder, e.g. "Order {par}": the value for each recipient, in "to" order.
button.personalisation.url_link.parameters
array · in body
Required if the link button is dynamic
For a "Visit website" button whose link ends with a placeholder: the end of the link for each recipient, in "to" order.
button.personalisation.copy_code.parameters
array · in body
Required if the template has a copy-code button
For a "Copy offer code" button (marketing templates): the coupon code each recipient copies, in "to" order.
button.personalisation.copy_otp_code.parameters
array · in body
Required if the template has a copy-OTP button
For a "Copy code" button on authentication (one-time password) templates: the OTP code for each recipient, in "to" order.
button.personalisation.quick_reply.parameters
object · in body
Optional
Optional custom payloads for quick-reply buttons. Keys are the button's position among the quick-reply buttons ("0" = first, "1" = second, …); each value is a list with one payload per recipient, in "to" order. When the person taps that button, the payload comes back to you in the reply callback, so you know who answered what. Without it, the button works with no payload.
reference
string · in body
Optional
Your own label for this send (up to 50 characters, no spaces).
date
string · in body
Required with time
Send later on this date, format YYYY-MM-DD (East Africa Time). Must be today or later.
time
string · in body
Required with date
Send later at this time, 24-hour format HH:MM (East Africa Time, UTC+3).
Only send the header and button parts your template actually has: a template has at most one header (image, document, location or text). "All fields" below shows every possible part at once, for reference only.
Every per-recipient list (personalisation, header.images, header.documents and each "parameters" list) must have one entry per number in "to", in the same order.
"message" in the response is the template text with its {placeholders}; each person receives their own values.
A WhatsApp message can produce several delivery callbacks: SENT, then DELIVERED, then READ.
Older field names (media.document, media.document_name) still work, but use header for new integrations.

Example request

curl -X POST 'https://messaging-service.co.tz/api/whatsapp/v2/text/single' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "account": "MyShop",
    "template": "order_update",
    "to": [
        255712345678,
        255754000111
    ],
    "personalisation": [
        {
            "name": "Asha",
            "order": "A12"
        },
        {
            "name": "John",
            "order": "B7"
        }
    ],
    "header": {
        "image": {
            "file": "https://example.com/banner.jpg"
        }
    },
    "button": {
        "personalisation": {
            "url_link": {
                "parameters": [
                    "A12",
                    "B7"
                ]
            }
        }
    }
}'

Example response · 200 OK

{
    "messages": [
        {
            "to": "255712345678",
            "status": {
                "groupId": 18,
                "groupName": "PENDING",
                "id": 51,
                "name": "ENROUTE (SENT)",
                "description": "Message sent to next instance"
            },
            "messageId": 7391827364512378881,
            "message": "Hi {name}, your order {order} has been shipped.",
            "smsCount": 1,
            "price": 50
        }
    ]
}
What does each field mean?
to
— The phone number.
status.groupName
— PENDING = accepted, REJECTED = not sent (see status.name for why).
status.name / status.id
— The exact status. See "Statuses" below.
messageId
— The ID of this message. Save it to match delivery reports and callbacks. It is a very large number: store it as text.
message
— The text that was sent.
smsCount
— SMS parts used per recipient.
price
— Amount charged for this recipient (left out when zero).
All fields (complete request skeleton, for reference)

Every possible field at once. Send only the parts your template uses.

{
    "to": [
        255712345678
    ],
    "account": "MyShop",
    "template": "order_confirmation",
    "personalisation": [
        {
            "name": "Asha",
            "number": "123456"
        }
    ],
    "header": {
        "image": {
            "file": "https://example.com/banner.png",
            "name": "",
            "personalisation": {
                "qr_code_url": {
                    "x_start": 40,
                    "y_start": 40,
                    "size": 20,
                    "parameters": [
                        "TKT-001"
                    ]
                }
            }
        },
        "images": [
            {
                "file": "https://example.com/asha.png",
                "name": ""
            }
        ],
        "document": {
            "file": "https://example.com/invoice.pdf",
            "caption": "",
            "name": "Invoice.pdf"
        },
        "documents": [
            {
                "file": "https://example.com/invoice-asha.pdf",
                "caption": "",
                "name": "Invoice-Asha.pdf"
            }
        ],
        "location": {
            "longitude": "39.2083",
            "latitude": "-6.7924",
            "name": "Our shop",
            "address": "Samora Ave, Dar es Salaam"
        },
        "text": {
            "personalisation": {
                "parameters": [
                    "A12"
                ]
            }
        }
    },
    "button": {
        "personalisation": {
            "copy_code": {
                "parameters": [
                    "SAVE10"
                ]
            },
            "quick_reply": {
                "parameters": {
                    "0": [
                        "confirm-A12"
                    ]
                }
            },
            "copy_otp_code": {
                "parameters": [
                    "482913"
                ]
            },
            "url_link": {
                "parameters": [
                    "A12"
                ]
            }
        }
    },
    "reference": "order-A12"
}
POST /api/{channel}/v2/text/multi Sends & charges

Send different messages in one request

Send many personalised messages (different text per person) in a single call.

Use this when each person should get their own text, for example "Hi Asha, your balance is 5,000". Put up to 500 messages in the "messages" list. Works for sms and mobile; WhatsApp is not supported here.

Fields

channel
string · in URL
Required
Where to send: sms, mobile or whatsapp. See "Channels".
messages
array · in body
Required
The list of messages, up to 500. Each item has "from", "to" (a number or a list of numbers in 255XXXXXXXXX format) and "text".
reference
string · in body
Optional
Your own label for the whole batch (up to 50 characters).
date
string · in body
Required with time
Send later on this date, format YYYY-MM-DD (East Africa Time). Must be today or later.
time
string · in body
Required with date
Send later at this time, 24-hour format HH:MM (East Africa Time, UTC+3).
Numbers must be in international format 255XXXXXXXXX here (local formats like 0712... are rejected).
The Sender ID must be enabled for bulk (broadcast) sending on your account.
Duplicate protection: if the first message of a request is exactly the same as the first message of a request sent in the last 24 hours, the request is refused with "Duplicate request sent within past 24 hour(s)".

Example request

curl -X POST 'https://messaging-service.co.tz/api/sms/v2/text/multi' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "messages": [
        {
            "from": "NEXTSMS",
            "to": "255712345678",
            "text": "Hi Asha, your balance is 5,000 TZS."
        },
        {
            "from": "NEXTSMS",
            "to": "255754000111",
            "text": "Hi John, your balance is 12,500 TZS."
        }
    ],
    "reference": "batch-77"
}'

Example response · 200 OK

{
    "messages": [
        {
            "to": "255712345678",
            "status": {
                "groupId": 18,
                "groupName": "PENDING",
                "id": 51,
                "name": "ENROUTE (SENT)",
                "description": "Message sent to next instance"
            },
            "messageId": 7391827364512378881,
            "message": "Hi Asha, your balance is 5,000 TZS.",
            "smsCount": 1,
            "price": 20
        },
        {
            "to": "255754000111",
            "status": {
                "groupId": 18,
                "groupName": "PENDING",
                "id": 51,
                "name": "ENROUTE (SENT)",
                "description": "Message sent to next instance"
            },
            "messageId": 7391827364512378882,
            "message": "Hi John, your balance is 12,500 TZS.",
            "smsCount": 1,
            "price": 20
        }
    ]
}
What does each field mean?
to
— The phone number.
status.groupName
— PENDING = accepted, REJECTED = not sent (see status.name for why).
status.name / status.id
— The exact status. See "Statuses" below.
messageId
— The ID of this message. Save it to match delivery reports and callbacks. It is a very large number: store it as text.
message
— The text that was sent.
smsCount
— SMS parts used per recipient.
price
— Amount charged for this recipient (left out when zero).
Example of a rejected number
{
    "to": "25571234",
    "status": {
        "groupId": 19,
        "groupName": "REJECTED",
        "id": 69,
        "name": "REJECTED_INVALID_DESTINATION",
        "description": "Phone Number Not Valid, not supported country codes"
    },
    "sendReference": 7391827364512378880,
    "smsCount": 0,
    "sort": 1
}

Test without sending

POST /api/{channel}/v2/test/text/single Free

Test a send (no message, no charge)

Check a "Send a message" request without sending anything or paying.

Exactly the same as "Send a message" (and the WhatsApp send), but nothing is sent, stored or charged. You get the same response, so you can check your Sender ID, numbers and message parts first. The messageId values in the answer are not real.

Fields

channel
string · in URL
Required
Where to send: sms, mobile or whatsapp. See "Channels".
…
object · in body
Required
The same body as "Send a message" or "Send a WhatsApp template message".
Your balance is not checked, so FAILED_NOT_ENOUGH_CREDITS never appears in a test.

Example request

curl -X POST 'https://messaging-service.co.tz/api/sms/v2/test/text/single' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "from": "NEXTSMS",
    "to": "255712345678",
    "text": "This is a test message."
}'

Example response · 200 OK

{
    "messages": [
        {
            "to": "255712345678",
            "status": {
                "groupId": 18,
                "groupName": "PENDING",
                "id": 51,
                "name": "ENROUTE (SENT)",
                "description": "Message sent to next instance"
            },
            "messageId": 7391827364512378881,
            "message": "Hello! Your order 1001 is ready for pickup.",
            "smsCount": 1,
            "price": 20
        }
    ]
}
What does each field mean?
to
— The phone number.
status.groupName
— PENDING = accepted, REJECTED = not sent (see status.name for why).
status.name / status.id
— The exact status. See "Statuses" below.
messageId
— The ID of this message. Save it to match delivery reports and callbacks. It is a very large number: store it as text.
message
— The text that was sent.
smsCount
— SMS parts used per recipient.
price
— Amount charged for this recipient (left out when zero).
POST /api/{channel}/v2/test/text/multi Free

Test a multi send (no message, no charge)

Check a "Send different messages" request without sending anything.

The same as "Send different messages in one request", but nothing is sent or charged.

Fields

channel
string · in URL
Required
Where to send: sms, mobile or whatsapp. See "Channels".
messages
array · in body
Required
The same body as "Send different messages in one request".
Tests do not count for the 24-hour duplicate protection, so you can test the same request as often as you like and then send it for real. A test does warn you if the real request would be refused as a duplicate.

Example request

curl -X POST 'https://messaging-service.co.tz/api/sms/v2/test/text/multi' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "messages": [
        {
            "from": "NEXTSMS",
            "to": "255712345678",
            "text": "Test for Asha"
        },
        {
            "from": "NEXTSMS",
            "to": "255754000111",
            "text": "Test for John"
        }
    ]
}'

Example response · 200 OK

{
    "messages": [
        {
            "to": "255712345678",
            "status": {
                "groupId": 18,
                "groupName": "PENDING",
                "id": 51,
                "name": "ENROUTE (SENT)",
                "description": "Message sent to next instance"
            },
            "messageId": 7391827364512378881,
            "message": "Hello! Your order 1001 is ready for pickup.",
            "smsCount": 1,
            "price": 20
        }
    ]
}
What does each field mean?
to
— The phone number.
status.groupName
— PENDING = accepted, REJECTED = not sent (see status.name for why).
status.name / status.id
— The exact status. See "Statuses" below.
messageId
— The ID of this message. Save it to match delivery reports and callbacks. It is a very large number: store it as text.
message
— The text that was sent.
smsCount
— SMS parts used per recipient.
price
— Amount charged for this recipient (left out when zero).
POST /api/{channel}/v2/sms_count Free

Count SMS parts

Find out how many SMS a text will use before you send it.

Long messages are split into parts and each part is charged. A normal (GSM) SMS holds 160 characters, or 153 per part when longer. Emojis and non-English characters switch to Unicode: 70 characters, or 67 per part.

Fields

channel
string · in URL
Required
Where to send: sms, mobile or whatsapp. See "Channels".
text
string · in body
Required
The message to measure.
WhatsApp always counts as 1.
Texts longer than 15 GSM parts or 10 Unicode parts are rejected when sending.

Example request

curl -X POST 'https://messaging-service.co.tz/api/sms/v2/sms_count' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
    "text": "Hello World"
}'

Example response · 200 OK

{
    "smsCount": 1,
    "charUsed": 11,
    "charRemaining": 149,
    "charPerSms": 160,
    "encoding": "GSM"
}
What does each field mean?
smsCount
— Number of SMS parts each recipient will use.
charUsed
— Characters in your text.
charRemaining
— Characters you can still add before another part is needed.
charPerSms
— Characters per part for this encoding.
encoding
— GSM (plain text) or UNICODE (emojis or special characters).

Reports & logs

GET /api/v2/reports Free

Get delivery reports

Find out which messages were delivered.

Returns delivery results (DELIVERED, UNDELIVERABLE, EXPIRED, …). Without messageId it works like an inbox: each call returns only reports you have not collected before, oldest first, and they are not returned again. Keep calling until "results" is empty. Prefer webhooks if your server can receive them.

Fields

messageId
string · in URL query
Optional
Look up the report of one message. This can be repeated as often as you want.
size
integer · in URL query
Optional
How many reports to return, up to 200. Default 200.
sentSince
string · in URL query
Optional
Only messages sent on or after this date, YYYY-MM-DD.
sentUntil
string · in URL query
Optional
Only messages sent on or before this date, YYYY-MM-DD.
sender
string · in URL query
Optional
Only messages from this Sender ID.
reference
string · in URL query
Optional
Only messages sent with this reference.
channel
string · in URL query
Optional
Only this channel: sms, mobile or whatsapp.
Only messages that already have a delivery result appear here. To list everything you sent, including pending messages, use "Get message logs".

Example request

curl -X GET 'https://messaging-service.co.tz/api/v2/reports?size=100' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'

Example response · 200 OK

{
    "results": [
        {
            "messageId": "7391827364512378881",
            "reference": "order-1001",
            "sentAt": "2026-10-20 10:15:00",
            "doneAt": "2026-10-20 10:15:07",
            "to": "255712345678",
            "channel": "Internet SMS",
            "from": "NEXTSMS",
            "smsCount": 1,
            "status": {
                "groupId": 20,
                "groupName": "DELIVERED",
                "id": 73,
                "name": "DELIVERED"
            },
            "delivery": "DELIVERED"
        }
    ]
}
What does each field mean?
messageId
— The ID returned when you sent the message.
reference
— The reference you sent with the message.
sentAt / doneAt
— When it was sent / when the final result arrived (East Africa Time).
to / from
— Recipient number and Sender ID.
channel
— Internet SMS, Mobile SMS or Whatsapp Message.
status.name
— The current status, for example DISPATCHED (sent to the network) or DELIVERED.
delivery
— The final delivery result (see "Delivery results"), or null while still waiting.
GET /api/v2/logs Free

Get message logs

List the messages you sent, with their current status.

A searchable history of sent messages, newest first. Unlike reports, logs can be fetched again and again. Use limit and offset to page through.

Fields

sentSince
string · in URL query
Optional
Only messages sent on or after this date, YYYY-MM-DD.
sentUntil
string · in URL query
Optional
Only messages sent on or before this date, YYYY-MM-DD.
from
string · in URL query
Optional
Only messages from this Sender ID.
to
string · in URL query
Optional
Only messages to this phone number (255XXXXXXXXX).
reference
string · in URL query
Optional
Only messages sent with this reference.
channel
string · in URL query
Optional
Only this channel: sms, mobile or whatsapp.
offset
integer · in URL query
Optional
Skip this many rows (for paging).
limit
integer · in URL query
Optional
How many rows to return, up to 200. Default 200.
Searching logs by messageId is not allowed. Use "Get delivery reports" with messageId instead.
The message text is included only when limit is 15 or less. Use "Get message logs with text" for that.

Example request

curl -X GET 'https://messaging-service.co.tz/api/v2/logs?sentSince=2026-10-01&sentUntil=2026-10-20&limit=50' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'

Example response · 200 OK

{
    "results": [
        {
            "messageId": "7391827364512378881",
            "reference": "order-1001",
            "sentAt": "2026-10-20 10:15:00",
            "doneAt": "2026-10-20 10:15:07",
            "to": "255712345678",
            "channel": "Internet SMS",
            "from": "NEXTSMS",
            "smsCount": 1,
            "status": {
                "groupId": 20,
                "groupName": "DELIVERED",
                "id": 73,
                "name": "DELIVERED"
            },
            "delivery": "DELIVERED"
        }
    ]
}
What does each field mean?
messageId
— The ID returned when you sent the message.
reference
— The reference you sent with the message.
sentAt / doneAt
— When it was sent / when the final result arrived (East Africa Time).
to / from
— Recipient number and Sender ID.
channel
— Internet SMS, Mobile SMS or Whatsapp Message.
status.name
— The current status, for example DISPATCHED (sent to the network) or DELIVERED.
delivery
— The final delivery result (see "Delivery results"), or null while still waiting.
GET /api/v2/msglogs Free

Get message logs with text

Like message logs, but each row includes the message text.

Returns 15 rows at a time by default, each with the "text" that was sent.

Fields

sentSince
string · in URL query
Optional
Only messages sent on or after this date, YYYY-MM-DD.
sentUntil
string · in URL query
Optional
Only messages sent on or before this date, YYYY-MM-DD.
from
string · in URL query
Optional
Only messages from this Sender ID.
to
string · in URL query
Optional
Only messages to this phone number (255XXXXXXXXX).
reference
string · in URL query
Optional
Only messages sent with this reference.
channel
string · in URL query
Optional
Only this channel: sms, mobile or whatsapp.
offset
integer · in URL query
Optional
Skip this many rows (for paging).
limit
integer · in URL query
Optional
How many rows to return. Default 15. Above 15 the text is left out.

Example request

curl -X GET 'https://messaging-service.co.tz/api/v2/msglogs?limit=15' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Accept: application/json'

Example response · 200 OK

{
    "results": [
        {
            "messageId": "7391827364512378881",
            "reference": "order-1001",
            "sentAt": "2026-10-20 10:15:00",
            "doneAt": "2026-10-20 10:15:07",
            "to": "255712345678",
            "channel": "Internet SMS",
            "from": "NEXTSMS",
            "smsCount": 1,
            "status": {
                "groupId": 20,
                "groupName": "DELIVERED",
                "id": 73,
                "name": "DELIVERED"
            },
            "delivery": "DELIVERED",
            "text": "Hello! Your order 1001 is ready for pickup."
        }
    ]
}
What does each field mean?
messageId
— The ID returned when you sent the message.
reference
— The reference you sent with the message.
sentAt / doneAt
— When it was sent / when the final result arrived (East Africa Time).
to / from
— Recipient number and Sender ID.
channel
— Internet SMS, Mobile SMS or Whatsapp Message.
status.name
— The current status, for example DISPATCHED (sent to the network) or DELIVERED.
delivery
— The final delivery result (see "Delivery results"), or null while still waiting.
text
— The message that was sent.

Statuses

Every number in a send response, report or log comes with a status, for example {"groupId": 18, "groupName": "PENDING", "id": 51, "name": "ENROUTE (SENT)"}. The groupName tells you the kind of result; the id and name tell you exactly what happened.

PENDING groupId 18

The message has been accepted and is on its way. The final result follows in a delivery report or callback.

idnameWhat happenedWhat to do
50 PENDING_WAITING_DELIVERY Sent to the mobile network, which acknowledged it. Waiting for the delivery report. —
51 ENROUTE (SENT) Accepted and passed on to the next step (mobile network, WhatsApp or your gateway SIM). This is the normal answer to a send. —
52 ACCEPTED Accepted and ready to go. Scheduled messages show this until their date and time. —
88 DISPATCHED Handed over to the operator. Message logs show this until the delivery report arrives. —
112 DISCARDED The operator dropped the message, so it will not be delivered. Send it again later; contact support if it keeps happening.

DELIVERY groupId 20

The final result reported by the mobile operator or WhatsApp.

idnameWhat happenedWhat to do
73 DELIVERED The phone received the message. —
74 EXPIRED The network could not deliver it in time, for example because the phone stayed off. Try again later.
75 DELETED The message was deleted before delivery. —
76 UNDELIVERABLE The network could not deliver it, for example because the number does not exist. Check the number.
77 ACCEPTED The network accepted the message and will send it. —
78 UNKNOWN The network returned an unknown result. —
79 REJECTED The network refused the message, for example because it blocks this number range. Contact support.
80 SKIPPED Not sent for another reason, such as operator downtime. Send it again later.
108 READ WhatsApp only: the person opened the message. —
109 SENT WhatsApp only: sent to WhatsApp, not yet on the phone. —
114 SPAM_RATE_LIMIT WhatsApp only: held back by Meta, which limits how many marketing messages one person receives. Send fewer marketing messages to the same people.
120 FAILED WhatsApp only: the message could not be delivered. Check that the number uses WhatsApp.

FAILED groupId 22

The message could not be processed. Note: in a send response these currently arrive with groupId 19 / groupName REJECTED.

idnameWhat happenedWhat to do
57 FAILED_NOT_ENOUGH_CREDITS Your balance ran out before this number. Top up your account.
58 FAILED_SENDER Your Sender ID is not registered on this person's network. Contact support to register it on that network.

REJECTED groupId 19

Not sent: refused by our system, or by the operator as a final result. Rejected numbers are not charged.

idnameWhat happenedWhat to do
53 REJECTED_NETWORK The number's network is not covered or not set up on your account. Ask your account manager about coverage.
54 REJECTED_PREFIX_MISSING The number is not recognised: wrong prefix or length. Use 255XXXXXXXXX.
55 REJECTED_DND The person has "Do Not Disturb" switched on. Remove the number from your list.
56 REJECTED_SOURCE The Sender ID ("from") is not registered or approved on your account. Check the spelling and capital letters, or request the Sender ID in your dashboard.
59 REJECTED_DESTINATION The number is blocked, or the person unsubscribed from your Sender ID. Remove the number from your list.
60 REJECTED_PREPAID_PACKAGE_EXPIRED Your credits have passed their validity period. Top up your account.
61 REJECTED_DESTINATION_NOT_REGISTERED Your account is limited to one test number. Ask your account manager to remove the limit.
62 REJECTED_ROUTE_NOT_AVAILABLE Your account has no route set up for sending. Ask your account manager to set it up.
63 REJECTED_FLOODING_FILTER Too many messages to one number: by default 20 different or 6 identical messages per hour. Wait, or ask your account manager to raise the limit.
64 REJECTED_SYSTEM_ERROR An unexpected error on our side. Try again, or contact support.
65 REJECTED_DUPLICATE_MESSAGE_ID The same message ID was used twice. Use a unique ID per message.
66 REJECTED_INVALID_UDH The message is formatted incorrectly (invalid binary header). Contact support.
67 REJECTED_MESSAGE_TOO_LONG The message is longer than 15 SMS parts (10 for Unicode). Shorten the message.
68 MISSING_TO "to" is missing or empty. Add at least one phone number.
69 REJECTED_INVALID_DESTINATION The phone number is not valid: wrong network prefix or length. Use 255XXXXXXXXX with a real network prefix.
107 REJECTED_PRICE_NOT_DEFINED No price is set for this channel or network on your account. Contact support.
110 REJECTED_EXCEEDED_MESSAGE_LIMIT You reached your sending limit for the period. Wait for the period to pass, or ask support to raise your limit.
111 REJECTED_MISSING_OUTLET No active way to send: no active WhatsApp account and template (WhatsApp), or no active gateway app and SIM card (Mobile SMS). Connect and activate the WhatsApp account or gateway app.

Webhooks (callbacks)

Instead of asking for reports, let us tell you. When a delivery result arrives, we send it straight to an address on your server.

  1. Create a page on your server that accepts POST requests with a JSON body, for example https://yourdomain.com/nextsms/callback.
  2. In the dashboard open Customer Profile (top-right menu) → Customization → Webhooks, enter that address as Delivery Callback URL and choose a secret Verify Token. We send a test request when you save, so your page must answer with HTTP 200.
  3. Send messages as usual. Only messages sent after the URL is saved get callbacks.
  4. On each callback, check that token equals your Verify Token, save the result by messageId, and reply 200 quickly (we wait 5 seconds and do not retry).

Delivery result

{
    "token": "YOUR_CALLBACK_VERIFY_TOKEN",
    "messageId": "7391827364512378881",
    "status": "DELIVERED",
    "id": "73"
}

status and id are from Delivery results. WhatsApp can send SENT, DELIVERED and READ for the same message.

WhatsApp button reply

{
    "token": "YOUR_CALLBACK_VERIFY_TOKEN",
    "messageId": "7391827364512378881",
    "response": "Yes",
    "payload": "confirm-order-1001",
    "text": "Yes"
}

Sent when someone taps a quick-reply button on your WhatsApp template.

Example receiver (PHP)

<?php
$data = json_decode(file_get_contents('php://input'), true, 512, JSON_BIGINT_AS_STRING);

if (($data['token'] ?? '') !== 'YOUR_CALLBACK_VERIFY_TOKEN') {
    http_response_code(401);
    exit;
}

// Save $data['messageId'] and $data['status'] in your database here.

http_response_code(200);

Errors & troubleshooting

401 Your token is wrong, or the account is not active.

Fix: Copy the token again from Customer Profile (top-right menu) → Customization → API Keys and send it as "Authorization: Bearer <token>".

{
    "success": false,
    "status": 401,
    "message": "Not Authorized, Customer Error",
    "errors": {
        "Authenticate": "Token"
    }
}

422 A field is missing or has the wrong format.

Fix: Read "errors": it lists every field that needs fixing.

{
    "message": "The from field is required. (and 1 more error)",
    "errors": {
        "from": [
            "The from field is required."
        ],
        "text": [
            "The text field is required."
        ]
    }
}

429 Too many failed sign-in attempts: 5 wrong passwords for one username, or 30 failed sign-ins of any kind, from your IP address within 15 minutes.

Fix: Wait for the time in the Retry-After header (seconds), then try again with the correct token. Successful requests are never limited.

{
    "success": false,
    "status": 429,
    "message": "Too many failed sign-in attempts. Try again in 15 minute(s).",
    "errors": {
        "Authenticate": "Token"
    }
}

500 Something went wrong on our side, or the request could not be read.

Fix: Check that your JSON is valid, then try again. If it continues, contact support with the time of the request.

{
    "success": false,
    "status": 500,
    "message": "Error processing send request, contact support!!"
}

Checklist when something does not work

  • Is the header exactly Authorization: Bearer <token> (one space, no quotes)? Did you use a /v2/ URL with a token?
  • Did you send Accept: application/json? Without it, mistakes come back as a redirect page instead of a clear JSON error.
  • Is your body valid JSON? Double quotes only, no comma after the last item.
  • Is the Sender ID spelled exactly as approved, with the same capital letters?
  • Are numbers in 255XXXXXXXXX format?
  • Got 200 but no message? Read each number's status in the response, then the delivery report.
  • Getting "Duplicate request" on multi send? The first message is the same as in a real request sent in the last 24 hours. Change it, or wait.

Limits

Messages per multi requestUp to 500 items in "messages".
Message lengthUp to 15 SMS parts (GSM) or 10 parts (Unicode).
Report and log page sizeUp to 200 rows per call.
Failed sign-ins5 wrong passwords for one username, or 30 failed sign-ins in total, per IP address in 15 minutes; then 429 until the 15 minutes pass. Successful requests are not limited.
Internet SMSA per-account limit per 24 hours (ask support for yours).
Mobile SMS300 messages per hour for each connected SIM card.
WhatsAppYour WhatsApp account's daily messaging limit set by Meta.
WhatsApp mediaImages: PNG or JPEG up to 3 MB. Documents: PDF up to 1 MB.

Use with AI assistants

Want ChatGPT, Claude, Gemini or Copilot to write the integration for you? Download one of these files and attach it to your chat (or give the assistant its link, if it can open web pages). They describe the whole API v2, generated from the same source as this page, so they are always up to date.

Guide for AI assistants (TXT)

The full guide in plain text, in the llms.txt format. Attach it to a chat with an AI assistant.

OpenAPI 3.1 (JSON)

Import into Postman, Insomnia, code generators or AI agents.

Example prompt

Prompt
Using the attached NextSMS API guide (nextsms-api-v2-llms.txt), write a [PHP / Python / Node.js] function that sends an SMS with sender ID "NEXTSMS" to a list of phone numbers. Read the API token from environment variables, send Accept: application/json, and check the status of every recipient in the response. First use the /test/ endpoint.

Questions

Do I need to be a programmer?

No. You can try every request on this page with the "Try it" buttons, or import the Postman collection. If you use a ready-made system (school, SACCO, POS, CRM), give your developer this page and your API token.

Where do I get my API token?

Log in to your dashboard and open Customer Profile (top-right menu) → Customization → API Keys. If the box is empty, click Generate Token. Keep it secret like a password; anyone with it can send messages using your balance. Generating again later replaces the old token.

Which phone number format should I use?

Always 255 followed by the 9-digit number, without + or spaces, for example 255712345678.

I got status 200 but the message did not arrive.

Look at the status of each number in the response, then at the delivery report. REJECTED means it was never sent (see the status table). UNDELIVERABLE or EXPIRED means the network could not reach the phone.

What time zone does scheduling use?

East Africa Time (Dar es Salaam, UTC+3).

Can I send to many numbers at once?

Yes. Put a list of numbers in "to" for the same text, or use the multi endpoint for different texts.

How do I know when a message is delivered?

Set a Delivery Callback URL in Customer Profile (top-right menu) → Customization → Webhooks and we will POST each result to your server, or collect them with "Get delivery reports".

Prefer Postman?

Our Postman documentation for API v2 has a ready-made collection you can open in Postman and run.

Open Postman documentation ↗

Need help? Contact support.