One HTTPS endpoint to send SMS to 200 countries, with real-time delivery reports, GSM & Unicode support, and automatic long-message concatenation. JSON in, JSON out.
Sign up or ask your account manager for an API username, password and your dedicated sending host.
Send a single JSON request to the submission endpoint — auth, sender, recipient and message text.
We call your dlrUrl in real time as the message moves toward the handset.
<your-api-host> in place of your real submission host. Your account manager will give you the live hostname, username and password — swap them in before you go live.curl -L "https://<your-api-host>/bulk/sendsms" \
-X POST \
-H "Content-Type: application/json" \
-d '{
"type": "text",
"auth": { "username": "your_username", "password": "your_password" },
"sender": "SMSwarriors",
"receiver": "4179123456",
"dcs": "GSM",
"text": "Hello from SMSwarriors!",
"dlrMask": 19,
"dlrUrl": "https://your-app.com/dlr-callback"
}'
{ "msgId": "9325d0a8-2638-11e6-afe7-bffc7cc8fa4f", "numParts": 1 }
There's no separate API key or OAuth flow. Every request carries an auth object with the username and password issued to your account. Always send requests over HTTPS so credentials stay encrypted in transit.
"auth": { "username": "your_username", "password": "your_password" }
Must be on your account's IP allowlist, or requests fail with RC_IP_NOT_ALLOWED.
HTTPS strongly recommended for all production traffic.
Contact support to rotate a compromised password immediately.
Send one JSON object per request. The same endpoint handles single messages and each message in a bulk job — call it once per recipient, or fan requests out from your own queue. There are three message types: GSM-encoded text, Unicode text, and WAP Service Indication (WSI) links — pick the one you need with type and dcs.
| Parameter | Type | Required | Description |
|---|---|---|---|
type | string | Required | Message type — "text" or "wsi". |
sender | string | Required | Alphanumeric or numeric originator address (your sender ID). |
receiver | string | Required | Destination number in E.164 format, without the leading + (e.g. 4179123456). |
auth | object | Required | Your username / password credentials. |
dlrMask | integer | Optional | Bitmask of DLR events to subscribe to. Default 19 (all final statuses). See Delivery reports. |
dlrUrl | string | Optional | HTTPS callback URL we POST delivery reports to. |
flash | boolean | Optional | Deliver as a Flash SMS (displays immediately, not stored on the handset). |
validityPeriodMinutes | integer | Optional | How long the message stays valid for delivery attempts. Defaults to 24 hours if omitted. |
custom | object | Optional | Free-form JSON echoed back verbatim in every DLR for this message — handy for correlating with your own IDs. |
| Parameter | Type | Required | Description |
|---|---|---|---|
text | string | Required | UTF-8 encoded message body. |
dcs | string | Required | Character encoding — "GSM" for standard GSM 03.38, or "UCS" for Unicode (emoji, non-Latin scripts). |
CONTENT='{
"type": "text",
"auth": {"username": "your_username", "password": "your_password"},
"sender": "SMSwarriors",
"receiver": "4179123456",
"dcs": "GSM",
"text": "This is a test message",
"dlrMask": 19,
"dlrUrl": "https://your-app.com/dlr-callback"
}'
curl -L "https://<your-api-host>/bulk/sendsms" \
-H "Content-Type: application/json" \
-X POST -d "$CONTENT"
const res = await fetch("https://<your-api-host>/bulk/sendsms", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
type: "text",
auth: { username: "your_username", password: "your_password" },
sender: "SMSwarriors",
receiver: "4179123456",
dcs: "GSM",
text: "This is a test message",
dlrMask: 19,
dlrUrl: "https://your-app.com/dlr-callback",
}),
});
const data = await res.json();
console.log(data.msgId, data.numParts);
$payload = json_encode([
"type" => "text",
"auth" => ["username" => "your_username", "password" => "your_password"],
"sender" => "SMSwarriors",
"receiver" => "4179123456",
"dcs" => "GSM",
"text" => "This is a test message",
"dlrMask" => 19,
"dlrUrl" => "https://your-app.com/dlr-callback",
]);
$ch = curl_init("https://<your-api-host>/bulk/sendsms");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
CURLOPT_RETURNTRANSFER => true,
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
{ "type": "text", "auth": {"username": "your_username", "password": "your_password"}, "sender": "SMSwarriors", "receiver": "4179123456", "dcs": "UCS", "text": "Message with UTF-8 characters üöä€ 你好", "dlrMask": 19, "dlrUrl": "https://your-app.com/dlr-callback" }
Use "dcs": "UCS" for emoji, Arabic, Cyrillic, CJK or any character outside the GSM 03.38 alphabet. We convert UTF-8 input to UTF-16 automatically — no manual encoding needed on your side.
A WSI delivers a clickable link with a title, rendered natively by the handset — useful for one-tap links in place of a plain-text URL. Use the same endpoint with type: "wsi".
| Parameter | Type | Required | Description |
|---|---|---|---|
url | string | Required | URL displayed and opened by the WSI. |
title | string | Required | Title text shown alongside the link. |
{ "type": "wsi", "auth": {"username": "your_username", "password": "your_password"}, "sender": "SMSwarriors", "receiver": "41787078880", "url": "https://smswarriors.com/", "title": "SMSwarriors", "dlrMask": 19, "dlrUrl": "https://your-app.com/dlr-callback" }
{ "msgId": "9325d0a8-2638-11e6-afe7-bffc7cc8fa4f", "numParts": 2 }
msgId is a UUID you'll see again in every DLR for this message. numParts tells you how many physical SMS segments the message was split into (billing is per part).
{ "error": { "code": "107", "message": "Invalid sender" } }
The submission was rejected outright — see the full error code table below for what to fix.
105 (throttling). For a 500 server error, wait at least 1 minute before retrying.Returned in the error.code field of a 420 response.
| Code | Name | Meaning |
|---|---|---|
101 | RC_APPLICATION_ERROR | System-level application failure. |
102 | RC_ENCODING_ERROR | Unsupported encoding, or encoding doesn't match content. |
103 | RC_NO_ACCOUNT | Invalid username/password combination. |
104 | RC_IP_NOT_ALLOWED | Sending IP address isn't on your account's allowlist. |
105 | RC_THROTTLING_ERROR | Rate limit exceeded. Safe to retry after 1 second. |
106 | RC_BLACKLISTED_SENDER | Sender ID is blacklisted at the destination. |
107 | RC_INVALID_SENDER | Illegal characters in the sender field. |
108 | RC_MESSAGE_TOO_LONG | Message exceeds the maximum allowed length. |
109 | RC_BAD_CONTENT_FORMAT | The text parameter format is invalid. |
110 | RC_MISSING_MANDATORY_PARAMETER | A required field was omitted. |
111 | RC_UNKNOWN_MESSAGE_TYPE | type isn't "text" or "wsi". |
112 | RC_BAD_PARAMETER_VALUE | A parameter value is malformed. |
113 | RC_NO_CREDIT | Insufficient account balance. |
114 | RC_NO_ROUTE | No delivery route to the destination. |
115 | RC_CONCAT_ERROR | Concatenation would exceed the maximum SMS part limit. |
116 | RC_LOOP_DETECTED | A message loop was detected and the send was blocked. |
If you set dlrUrl on submission, we POST a JSON delivery report to that URL as the message moves through the network — from acceptance by the carrier through to the handset (or failure).
Every message resolves to one of four outcomes. Each has its own reasons and its own timing.
Delivered to the handset as fast as possible. Once the destination confirms, we send a DELIVERED DLR.
Delivery to the handset failed and no further attempts are made. Common causes: nonexistent number, no route to destination, the validity period expiring (24 hours by default), or a permanent network error.
Can happen two ways: immediately, as an HTTP 420 error response to your submission (invalid credentials, no credit, bad sender, malformed request) — or later, as a REJECTED DLR after we'd already accepted it (restricted destination, unsupported content, disabled account).
A temporary hold — absent subscriber, full handset buffer, or a network/device issue. If your dlrMask includes it, you get one DLR per retry attempt. Retries stop on success, a permanent error, or when the validity period expires.
Choose which events you want with dlrMask — add the values together. Default is 19 (Delivered + Undelivered + Rejected — the three final states).
| Event | Mask value | Status | Meaning |
|---|---|---|---|
| DELIVERED | 1 | Final | Confirmed delivery to the handset. |
| UNDELIVERED | 2 | Final | Delivery to the handset failed. |
| BUFFERED | 4 | Temporary | Queued at the SMSC, retry in progress. |
| SENT_TO_SMSC | 8 | Temporary | Handed off to the carrier's SMSC. |
| REJECTED | 16 | Final | Rejected at the SMSC layer. |
{ "msgId": "9325d0a8-2638-11e6-afe7-bffc7cc8fa4f", "event": "DELIVERED", "errorCode": 0, "errorMessage": "", "partNum": 0, "numParts": 1, "accountName": "your_username", "sendTime": 0, "dlrTime": 2, "custom": {} }
| Field | Type | Description |
|---|---|---|
msgId | string | Matches the msgId from the original submission response. |
event | string | One of DELIVERED, UNDELIVERED, BUFFERED, SENT_TO_SMSC, REJECTED. |
errorCode | integer | Failure reason code — 0 means no error. See table below. |
errorMessage | string | Human-readable description of the error, if any. |
partNum | integer | Zero-based index of this part within a concatenated message. |
numParts | integer | Total parts in the message (1 for a single SMS). |
accountName | string | Your account username. |
sendTime | integer | Seconds from submission to SMSC delivery. |
dlrTime | integer | Seconds from SMSC delivery to this report. |
custom | object | Echo of the custom object you sent at submission, if any. |
mcc / mnc | string | Mobile Country/Network Code — account-dependent, not always present. |
country | string | ISO2 destination country — account-dependent. |
price / currency | string | Informational cost of the SMS — account-dependent. |
msgId but with increasing partNum (0, 1, …) up to numParts − 1. Wait for every part before deciding a message fully delivered.Full list — most of these come straight from the mobile network (HLR/MSC/SGSN-level failures); the ones above 500 are gateway-level.
| Code | Meaning |
|---|---|
0 | No error. |
1 | Unknown subscriber. |
9 | Illegal subscriber. |
11 | Teleservice not provisioned. |
13 | Call barred. |
15 | CUG (closed user group) reject. |
19 | No SMS support in MS (handset). |
20 | Error in MS (handset). |
21 | Facility not supported. |
22 | Memory capacity exceeded. |
29 | Absent subscriber — handset offline or unreachable. |
30 | MS busy for MT SMS. |
36 | Network/protocol failure. |
44 | Illegal equipment. |
60 | No paging response. |
61 | GMSC congestion. |
63 | HLR timeout. |
64 | MSC/SGSN timeout. |
70 | SMRSE/TCP error. |
72 | MT congestion. |
75 | GPRS suspended. |
80 | No paging response via MSC. |
81 | IMSI detached. |
82 | Roaming restriction. |
83 | Deregistered in HLR for GSM. |
84 | Purged for GSM. |
85 | No paging response via SGSN. |
86 | GPRS detached. |
87 | Deregistered in HLR for GPRS. |
88 | MS purged for GPRS. |
89 | Unidentified subscriber via MSC. |
90 | Unidentified subscriber via SGSN. |
112 | Originator missing credit on prepaid account. |
113 | Destination missing credit on prepaid account. |
114 | Error in prepaid system. |
500 | Other error. |
989 | Supplier rejected the SMS. |
990 | HLR failure. |
991 | Rejected by message text filter. |
992 | Ported numbers not supported on destination. |
993 | Blacklisted sender. |
994 | Account has no credit. |
995 | Undeliverable number. |
996 | Validity period expired before delivery. |
997 | Blacklisted recipient. |
998 | No route to destination. |
999 | Repeated submission — possible loop detected. |
Messages longer than one segment are split automatically — no extra work required — but part count affects billing, so it's worth knowing the limits.
numParts in the submission response to know exactly what a message will cost.Letters and digits (0–9, a–z, A–Z) are always supported. Beyond that, only the special characters below are allowed in an alphanumeric sender ID.
| Supported | ASCII code | Not supported | ASCII code |
|---|---|---|---|
| SPACE | 0x20 | $ | 0x24 |
| ! | 0x21 | @ | 0x40 |
| " | 0x22 | [ | 0x5B |
| # | 0x23 | \ | 0x5C |
| % | 0x25 | ] | 0x5D |
| & | 0x26 | ^ | 0x5E |
| ' | 0x27 | _ | 0x5F |
| ( | 0x28 | ` | 0x60 |
| ) | 0x29 | { | 0x7B |
| * | 0x2A | | | 0x7C |
| + | 0x2B | } | 0x7D |
| , | 0x2C | ~ | 0x7E |
| - | 0x2D | € | — |
| . | 0x2E | — | — |
| / | 0x2F | — | — |
| : | 0x3A | — | — |
| ; | 0x3B | — | — |
| < | 0x3C | — | — |
| = | 0x3D | — | — |
| > | 0x3E | — | — |
| ? | 0x3F | — | — |
The GSM 03.38 default 7-bit alphabet used for "dcs": "GSM" message bodies. Find a character's row (low nibble) and column (high nibble) to get its code point — e.g. A is row 1, column 0x40 → 0x41.
| Dec / Hex | 0x0_ | 0x1_ | 0x2_ | 0x3_ | 0x4_ | 0x5_ | 0x6_ | 0x7_ |
|---|---|---|---|---|---|---|---|---|
| 0 / 0x0 | @ | Δ | SP | 0 | ¡ | P | ¿ | p |
| 1 / 0x1 | £ | _ | ! | 1 | A | Q | a | q |
| 2 / 0x2 | $ | Φ | " | 2 | B | R | b | r |
| 3 / 0x3 | ¥ | Γ | # | 3 | C | S | c | s |
| 4 / 0x4 | è | Λ | ¤ | 4 | D | T | d | t |
| 5 / 0x5 | é | Ω | % | 5 | E | U | e | u |
| 6 / 0x6 | ù | Π | & | 6 | F | V | f | v |
| 7 / 0x7 | ì | Ψ | ' | 7 | G | W | g | w |
| 8 / 0x8 | ò | Σ | ( | 8 | H | X | h | x |
| 9 / 0x9 | Ç | Θ | ) | 9 | I | Y | i | y |
| 10 / 0xA | LF | Ξ | * | : | J | Z | j | z |
| 11 / 0xB | Ø | ESC | + | ; | K | Ä | k | ä |
| 12 / 0xC | ø | Æ | , | < | L | Ö | l | ö |
| 13 / 0xD | CR | æ | - | = | M | Ñ | m | ñ |
| 14 / 0xE | Å | ß | . | > | N | Ü | n | ü |
| 15 / 0xF | å | É | / | ? | O | § | o | à |
A few characters aren't in the base table — they're sent as a 2-character escape sequence (ESC + code below), so each one counts as 2 characters against the GSM segment limit.
| Character | Escape sequence |
|---|---|
| € | ESC 0x65 |
| Form feed | ESC 0x0A |
| [ | ESC 0x3C |
| \ | ESC 0x2F |
| ] | ESC 0x3E |
| ^ | ESC 0x14 |
| { | ESC 0x28 |
| | | ESC 0x40 |
| } | ESC 0x29 |
| ~ | ESC 0x3D |
Submitting faster than your account's allowed rate returns error 105 — RC_THROTTLING_ERROR. This is the one rejection that's safe to retry automatically.
105 (throttling): back off ~1 second, then resubmit.500: wait at least 1 minute before retrying.Get your API username, password and sending host — most accounts are ready to send test messages the same day.
High-volume and wholesale accounts can bind directly over SMPP for lower latency and tighter throughput control — endpoints, bind credentials and throughput tiers.