Send SMS
Send the same message to one number, a list, or up to 10,000 numbers.
How big sends work
| Recipients | Response | Message IDs |
|---|---|---|
| 1 (as a string) | 201 with a single message | In the response |
| A list of up to 50 | 201 with a messages array | In the response |
| A list of 51 – 10,000 | 202 batch receipt | Via webhooks or GET /v1/sms/batches/{batch_id} |
Large lists are sent in the background, so you get an answer immediately instead of waiting for thousands of messages.
Request
| Field | Type | Description |
|---|---|---|
torequired | string | string[] | One phone number, or a list of up to 10,000. Kenyan formats (0712…, 712…, 254712…) are converted to E.164. Duplicates are removed. |
messagerequired | string | Message text, up to 1,600 characters. See SMS units for how length becomes parts. |
sender_id | string | An approved sender ID of your organisation, e.g. MYCOMPANY (not case-sensitive). Defaults to your default sender ID. See Sender IDs. |
callback_url | string | Where to send delivery reports for this request. Overrides the key's webhook URL. See Webhooks. |
Optional header: Idempotency-Key — makes retries safe. See Idempotency.
- cURL
- PHP
- Python
- Node.js
curl -X POST https://api.cloudmetis.com/v1/sms/send \
-H "Authorization: Bearer cm_live_your_key" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: otp-7f3a9c" \
-d '{
"to": "+254712345678",
"message": "Your verification code is 482913.",
"sender_id": "MYCOMPANY"
}'
$ch = curl_init('https://api.cloudmetis.com/v1/sms/send');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CLOUDMETIS_API_KEY'),
'Content-Type: application/json',
'Idempotency-Key: otp-7f3a9c',
],
CURLOPT_POSTFIELDS => json_encode([
'to' => '+254712345678',
'message' => 'Your verification code is 482913.',
'sender_id' => 'MYCOMPANY',
]),
]);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$body = json_decode(curl_exec($ch), true);
import os, requests
response = requests.post(
"https://api.cloudmetis.com/v1/sms/send",
headers={
"Authorization": f"Bearer {os.environ['CLOUDMETIS_API_KEY']}",
"Idempotency-Key": "otp-7f3a9c",
},
json={
"to": "+254712345678",
"message": "Your verification code is 482913.",
"sender_id": "MYCOMPANY",
},
timeout=30,
)
response.raise_for_status()
print(response.json()["message_id"])
const response = await fetch('https://api.cloudmetis.com/v1/sms/send', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CLOUDMETIS_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': 'otp-7f3a9c',
},
body: JSON.stringify({
to: '+254712345678',
message: 'Your verification code is 482913.',
sender_id: 'MYCOMPANY',
}),
});
if (!response.ok) throw new Error((await response.json()).error.message);
const data = await response.json();
Response: one number (201)
{
"status": "accepted",
"message_id": "msg_247",
"to": "+254712345678",
"message_status": "sent",
"recipients": 1,
"sms_parts": 1,
"batch_id": "bat_107",
"units_used": 1,
"units_available": 4999,
"sandbox": false
}
message_status is the message's state at the moment of the response — usually sent (live) or delivered (sandbox). Later changes arrive by webhook.
Response: a list of up to 50 (201)
{
"status": "accepted",
"recipients": 2,
"messages": [
{"message_id": "msg_185", "to": "+254712000101", "status": "sent", "sms_parts": 1},
{"message_id": "msg_186", "to": "+254733000102", "status": "sent", "sms_parts": 1}
],
"batch_id": "bat_105",
"units_used": 2,
"units_available": 4997,
"sandbox": false
}
A number that has opted out appears with "status": "optedout", "message_id": null and "sms_parts": 0 — it is not sent and uses no units.
Response: 51 to 10,000 numbers (202)
{
"status": "queued",
"batch_id": "bat_106",
"recipients": 60,
"opted_out": 0,
"units_required": 60,
"units_available": 4937,
"sandbox": false
}
The whole batch is checked first — numbers, sender ID and units. If anything is wrong, you get an error and nothing is queued. Once queued, the batch is sent within seconds; follow it with GET /v1/sms/batches/bat_106 or webhooks.
Response fields
| Field | Description |
|---|---|
status | accepted (sent now) or queued (batch, sent in the background) |
message_id / messages[].message_id | Use with GET /v1/sms/status/{message_id} |
message_status / messages[].status | See statuses |
batch_id | Every request gets one; use with GET /v1/sms/batches/{batch_id} |
recipients | Numbers in the request after removing duplicates (for batches, opted-out numbers are counted in opted_out instead) |
opted_out | Batches only: numbers skipped because they opted out |
units_used / units_required | Units this request uses — see SMS units |
units_available | Units left in your wallet |
sandbox | true for test keys |
Errors
| Status | Code | When |
|---|---|---|
| 400 | invalid_request | Missing to or message, more than 10,000 numbers, message over 1,600 characters, bad callback_url |
| 400 | invalid_phone_number | Any number is invalid — nothing is sent |
| 402 | insufficient_units | Not enough units for the whole request — nothing is sent |
| 403 | invalid_sender_id | sender_id not approved, or no approved sender ID |
| 403 | sms_not_configured | SMS not set up for your organisation (live keys) |
| 409 | idempotency_conflict | See Idempotency |
| 422 | all_recipients_opted_out | Every number has opted out |
| 502 | send_failed | The SMS network rejected the request |
Full list: Errors.