# NextSMS Messaging API v2 > Version 2 of the REST API: send SMS, Mobile SMS (from your own SIM via the Android gateway) and WhatsApp template messages in Tanzania, check balance and read delivery reports, signing in with an API token. JSON in, JSON out. - Base URL: https://messaging-service.co.tz - Human guide with "Try it" console: https://www.nextsms.co.tz/api-docs - OpenAPI 3.1: https://www.nextsms.co.tz/api-docs/openapi - Postman documentation: https://documenter.getpostman.com/view/1679195/2sAYkDP1XN - The other API version is described at: https://www.nextsms.co.tz/api-docs/llms?version=v1 ## Rules that apply to every request - Authentication: header `Authorization: Bearer `. The token comes from the dashboard: Customer Profile (top-right menu) → Customization → API Keys (click Generate Token if it is empty). All paths contain `/v2/`. - Always send `Accept: application/json` (otherwise a validation error is returned as an HTTP redirect instead of JSON) and `Content-Type: application/json` for POST. - Phone numbers: `255XXXXXXXXX` (country code, no `+`, no spaces). The multi endpoint accepts only this format. - `{channel}` is `sms` (Internet SMS with a Sender ID), `mobile` (Android gateway SIM; `from` is ignored) or `whatsapp` (template messages, single send only). An unknown value is treated as `sms`. - `messageId` is a 64-bit integer. Parse responses so that it stays exact (keep it as a string). - Scheduling: `date` (YYYY-MM-DD) + `time` (HH:MM) in East Africa Time (UTC+3). Repeat with `repeat` (daily|weekly|monthly|yearly) + `start_date` + `end_date` (also send `date` and `time`). - To validate a send without sending or paying, call the same path with `/test/` before `text`, e.g. `/api/sms/v2/test/text/single`. - A 200 send response can still contain rejected recipients: check `messages[].status.groupName` (PENDING = accepted, REJECTED = not sent). - No per-request callback URL: delivery callbacks go to the URL configured in the dashboard (Customer Profile (top-right menu) → Customization → Webhooks). ## Endpoints ### Check your balance `GET /api/v2/balance` Returns your available balance. Call it before a big campaign to make sure you have enough credit. Example: ```bash curl -X GET 'https://messaging-service.co.tz/api/v2/balance' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'Accept: application/json' ``` Response: ```json { "sms_balance": 125000.5, "over_draft": 0, "type": "price", "default_balance": 6250, "default": "Internet Channel", "display": "125,000.5 TZS" } ``` ### Send a message (SMS or Mobile SMS) `POST /api/{channel}/v2/text/single` 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". | Field | In | Type | Required | Description | |---|---|---|---|---| | `channel` | path | string | yes | Where to send: sms, mobile or whatsapp. See "Channels". | | `from` | body | string | for sms | Your approved Sender ID, exactly as registered (capital letters matter). Ignored for mobile. | | `to` | body | string|array | yes | 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` | body | string | yes | The message. Long texts are split into parts (see "Count SMS parts"). | | `reference` | body | string | no | Your own label for this send (up to 50 characters, no spaces). Use it later to find these messages in reports and logs. | | `flash` | body | integer | no | 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` | body | string | no | Two-letter country code used to understand local number formats. Default TZ. | | `date` | body | string | with time | Send later on this date, format YYYY-MM-DD (East Africa Time). Must be today or later. | | `time` | body | string | with date | Send later at this time, 24-hour format HH:MM (East Africa Time, UTC+3). | | `repeat` | body | string | for repeating | Repeat the scheduled message: daily, weekly, monthly or yearly. Send it together with date, time, start_date and end_date. | | `start_date` | body | string | for repeating | First day of the repeat period, YYYY-MM-DD. Not before date. | | `end_date` | body | string | for repeating | Last day of the repeat period, YYYY-MM-DD. Not before start_date. | Example: ```bash 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" }' ``` Response: ```json { "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 } ] } ``` - 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. ### Send a WhatsApp template message `POST /api/whatsapp/v2/text/single` 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". | Field | In | Type | Required | Description | |---|---|---|---|---| | `account` | body | string | yes | The name of your active WhatsApp account, as shown under WhatsApp → Accounts. | | `template` | body | string | yes | The name of an active, approved template. | | `to` | body | array | yes | List of WhatsApp numbers as numbers (not text), for example [255712345678]. Every per-recipient list below follows this order. | | `personalisation` | body | array | 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` | body | object | 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` | body | string | with header.image | A public https link to the image, or base64 data. PNG or JPEG, up to 3 MB. | | `header.image.name` | body | string | no | Optional file name for the image. Can be empty. | | `header.image.personalisation.qr_code_url` | body | object | 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` | body | number | 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` | body | number | 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` | body | number | 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` | body | array | with qr_code_url | The value put into the QR link for each recipient, in "to" order, e.g. their ticket number. | | `header.images` | body | array | 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` | body | object | 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` | body | string | with header.document | A public https link to the PDF, or base64 data. PDF only, up to 1 MB. | | `header.document.name` | body | string | no | The file name the recipient sees, e.g. "Invoice.pdf". | | `header.document.caption` | body | string | no | Optional text shown under the document. | | `header.documents` | body | array | 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` | body | object | 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` | body | string | with header.location | Latitude in decimal degrees. | | `header.location.longitude` | body | string | with header.location | Longitude in decimal degrees. | | `header.location.name` | body | string | with header.location | Name of the place. | | `header.location.address` | body | string | with header.location | Address of the place. | | `header.text.personalisation.parameters` | body | array | 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` | body | array | 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` | body | array | 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` | body | array | 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` | body | object | no | 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` | body | string | no | Your own label for this send (up to 50 characters, no spaces). | | `date` | body | string | with time | Send later on this date, format YYYY-MM-DD (East Africa Time). Must be today or later. | | `time` | body | string | with date | Send later at this time, 24-hour format HH:MM (East Africa Time, UTC+3). | Example: ```bash 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" ] } } } }' ``` All possible request fields (send only the parts the template uses): ```json { "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" } ``` Response: ```json { "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 } ] } ``` - 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. ### Send different messages in one request `POST /api/{channel}/v2/text/multi` 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. | Field | In | Type | Required | Description | |---|---|---|---|---| | `channel` | path | string | yes | Where to send: sms, mobile or whatsapp. See "Channels". | | `messages` | body | array | yes | 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` | body | string | no | Your own label for the whole batch (up to 50 characters). | | `date` | body | string | with time | Send later on this date, format YYYY-MM-DD (East Africa Time). Must be today or later. | | `time` | body | string | with date | Send later at this time, 24-hour format HH:MM (East Africa Time, UTC+3). | Example: ```bash 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" }' ``` Response: ```json { "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 } ] } ``` - 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)". ### Send with a simple link (GET) `GET /link/{channel}/v2/text/single` Some older systems, devices and spreadsheets can only open a link. Put the token and message details in the address. It works like "Send a message", with the same fields as query parameters. | Field | In | Type | Required | Description | |---|---|---|---|---| | `channel` | path | string | yes | Where to send: sms, mobile or whatsapp. See "Channels". | | `token` | query | string | yes | Your API token. (v1 uses username and password parameters instead.) | | `from` | query | string | for sms | Your approved Sender ID. | | `to` | query | string | yes | The phone number, 255XXXXXXXXX. | | `text` | query | string | yes | The message, URL-encoded (spaces become %20). | Example: ```bash curl -X GET 'https://messaging-service.co.tz/link/sms/v2/text/single?token=YOUR_API_TOKEN&from=NEXTSMS&to=255712345678&text=Hello%20from%20NextSMS' \ -H 'Accept: application/json' ``` Response: ```json { "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 } ] } ``` - Links are often saved in browser history and server logs, which exposes your token. Use the POST endpoints whenever you can. - Not for WhatsApp. ### Test a send (no message, no charge) `POST /api/{channel}/v2/test/text/single` 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. | Field | In | Type | Required | Description | |---|---|---|---|---| | `channel` | path | string | yes | Where to send: sms, mobile or whatsapp. See "Channels". | | `…` | body | object | yes | The same body as "Send a message" or "Send a WhatsApp template message". | Example: ```bash 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." }' ``` Response: ```json { "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 } ] } ``` - Your balance is not checked, so FAILED_NOT_ENOUGH_CREDITS never appears in a test. ### Test a multi send (no message, no charge) `POST /api/{channel}/v2/test/text/multi` The same as "Send different messages in one request", but nothing is sent or charged. | Field | In | Type | Required | Description | |---|---|---|---|---| | `channel` | path | string | yes | Where to send: sms, mobile or whatsapp. See "Channels". | | `messages` | body | array | yes | The same body as "Send different messages in one request". | Example: ```bash 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" } ] }' ``` Response: ```json { "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 } ] } ``` - 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. ### Count SMS parts `POST /api/{channel}/v2/sms_count` 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. | Field | In | Type | Required | Description | |---|---|---|---|---| | `channel` | path | string | yes | Where to send: sms, mobile or whatsapp. See "Channels". | | `text` | body | string | yes | The message to measure. | Example: ```bash 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" }' ``` Response: ```json { "smsCount": 1, "charUsed": 11, "charRemaining": 149, "charPerSms": 160, "encoding": "GSM" } ``` - WhatsApp always counts as 1. - Texts longer than 15 GSM parts or 10 Unicode parts are rejected when sending. ### Get delivery reports `GET /api/v2/reports` 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. | Field | In | Type | Required | Description | |---|---|---|---|---| | `messageId` | query | string | no | Look up the report of one message. This can be repeated as often as you want. | | `size` | query | integer | no | How many reports to return, up to 200. Default 200. | | `sentSince` | query | string | no | Only messages sent on or after this date, YYYY-MM-DD. | | `sentUntil` | query | string | no | Only messages sent on or before this date, YYYY-MM-DD. | | `sender` | query | string | no | Only messages from this Sender ID. | | `reference` | query | string | no | Only messages sent with this reference. | | `channel` | query | string | no | Only this channel: sms, mobile or whatsapp. | Example: ```bash curl -X GET 'https://messaging-service.co.tz/api/v2/reports?size=100' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'Accept: application/json' ``` Response: ```json { "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" } ] } ``` - Only messages that already have a delivery result appear here. To list everything you sent, including pending messages, use "Get message logs". ### Get message logs `GET /api/v2/logs` A searchable history of sent messages, newest first. Unlike reports, logs can be fetched again and again. Use limit and offset to page through. | Field | In | Type | Required | Description | |---|---|---|---|---| | `sentSince` | query | string | no | Only messages sent on or after this date, YYYY-MM-DD. | | `sentUntil` | query | string | no | Only messages sent on or before this date, YYYY-MM-DD. | | `from` | query | string | no | Only messages from this Sender ID. | | `to` | query | string | no | Only messages to this phone number (255XXXXXXXXX). | | `reference` | query | string | no | Only messages sent with this reference. | | `channel` | query | string | no | Only this channel: sms, mobile or whatsapp. | | `offset` | query | integer | no | Skip this many rows (for paging). | | `limit` | query | integer | no | How many rows to return, up to 200. Default 200. | Example: ```bash 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' ``` Response: ```json { "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" } ] } ``` - 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. ### Get message logs with text `GET /api/v2/msglogs` Returns 15 rows at a time by default, each with the "text" that was sent. | Field | In | Type | Required | Description | |---|---|---|---|---| | `sentSince` | query | string | no | Only messages sent on or after this date, YYYY-MM-DD. | | `sentUntil` | query | string | no | Only messages sent on or before this date, YYYY-MM-DD. | | `from` | query | string | no | Only messages from this Sender ID. | | `to` | query | string | no | Only messages to this phone number (255XXXXXXXXX). | | `reference` | query | string | no | Only messages sent with this reference. | | `channel` | query | string | no | Only this channel: sms, mobile or whatsapp. | | `offset` | query | integer | no | Skip this many rows (for paging). | | `limit` | query | integer | no | How many rows to return. Default 15. Above 15 the text is left out. | Example: ```bash curl -X GET 'https://messaging-service.co.tz/api/v2/msglogs?limit=15' \ -H 'Authorization: Bearer YOUR_API_TOKEN' \ -H 'Accept: application/json' ``` Response: ```json { "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." } ] } ``` ## Rejected recipient example ```json { "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 } ``` ## Status codes Every recipient in a send response, report or log has a `status` object with `groupId`, `groupName`, `id` and `name`. Codes by group: ### PENDING (groupId 18) The message has been accepted and is on its way. The final result follows in a delivery report or callback. | id | name | Meaning | What 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. | id | name | Meaning | What 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. | id | name | Meaning | What 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. | id | name | Meaning | What 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) Configure once in the dashboard: Customer Profile (top-right menu) → Customization → Webhooks → Delivery Callback URL and Verify Token. Only messages sent while a URL is configured get callbacks. We POST JSON (5-second timeout, no retries). Check that `token` equals your Verify Token, and reply with HTTP 200. Delivery callback: ```json { "token": "YOUR_CALLBACK_VERIFY_TOKEN", "messageId": "7391827364512378881", "status": "DELIVERED", "id": "73" } ``` WhatsApp button reply callback (same URL): ```json { "token": "YOUR_CALLBACK_VERIFY_TOKEN", "messageId": "7391827364512378881", "response": "Yes", "payload": "confirm-order-1001", "text": "Yes" } ``` ## Errors - HTTP 401: Your token is wrong, or the account is not active. Copy the token again from Customer Profile (top-right menu) → Customization → API Keys and send it as "Authorization: Bearer ". Example: `{"success":false,"status":401,"message":"Not Authorized, Customer Error","errors":{"Authenticate":"Token"}}` - HTTP 422: A field is missing or has the wrong format. Read "errors": it lists every field that needs fixing. Example: `{"message":"The from field is required. (and 1 more error)","errors":{"from":["The from field is required."],"text":["The text field is required."]}}` - HTTP 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. Wait for the time in the Retry-After header (seconds), then try again with the correct token. Successful requests are never limited. Example: `{"success":false,"status":429,"message":"Too many failed sign-in attempts. Try again in 15 minute(s).","errors":{"Authenticate":"Token"}}` - HTTP 500: Something went wrong on our side, or the request could not be read. Check that your JSON is valid, then try again. If it continues, contact support with the time of the request. Example: `{"success":false,"status":500,"message":"Error processing send request, contact support!!"}` ## Limits - Messages per multi request: Up to 500 items in "messages". - Message length: Up to 15 SMS parts (GSM) or 10 parts (Unicode). - Report and log page size: Up to 200 rows per call. - Failed sign-ins: 5 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 SMS: A per-account limit per 24 hours (ask support for yours). - Mobile SMS: 300 messages per hour for each connected SIM card. - WhatsApp: Your WhatsApp account's daily messaging limit set by Meta. - WhatsApp media: Images: PNG or JPEG up to 3 MB. Documents: PDF up to 1 MB.