No announcement

SMS API documentation

Send OTP, transactional and promotional SMS from your application with a simple REST API, and get the delivery status of every number.

Getting started

  1. Create an account and wait for it to be activated.
  2. In the panel, open DLT templates and add your entity ID, sender ID and approved templates.
  3. Open API and webhooks and create an API key.

All requests go to this base URL over HTTPS. Request and response bodies are JSON.

Base URL
https://cloudsms.in/api/v1

Authentication

Send your API key in the Authorization header of every request.

Authorization: Bearer csk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Keep the key on your server and never put it in a mobile app or web page. You can also limit the key to your server's IP addresses in the panel. If your system cannot set headers, you may pass api_key as a query parameter instead.

Send SMS

POST /sms/send

Sends one approved DLT template to one or more numbers. Each {#var#} in the template is filled, in order, from variables.

FieldRequiredDescription
template_idYesDLT content template ID, as saved in the panel.
variablesIf the template has valuesArray of values, or one string separated by |. Up to 30 characters each.
toYesArray of 10-digit mobile numbers, or one comma-separated string. +91 and 0 are removed.
senderNoSender ID. If given, it must match the template's sender.
schedule_atNoSend later, for example 2026-11-01T10:30:00+05:30. 5 minutes to 30 days ahead.
curl -X POST https://cloudsms.in/api/v1/sms/send \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "1207168xxxxxxxx412",
    "variables": ["Ravi", "482913"],
    "to": ["9876543210", "9123456780"]
  }'

Response when the numbers are handed to the operators:

HTTP 200
{
  "success": true,
  "batch_id": 1042,
  "request_id": "SLBZPIxP4mRVdf4",
  "status": "submitted",
  "sender": "ACMEIN",
  "template_id": "1207168xxxxxxxx412",
  "numbers": 2,
  "sms_parts": 1,
  "credits": 2,
  "sent": 2,
  "failed": 0,
  "scheduled_at": null,
  "created_at": "2026-10-22T10:15:02+05:30",
  "invalid_numbers": 0
}

Requests with more than 200 numbers return 202 Accepted with status: "queued" and are sent in the background. Use delivery reports to follow them.

For systems that can only call a URL, the same request works as a GET:

https://cloudsms.in/api/v1/sms/send?api_key=YOUR_API_KEY&template_id=1207168xxxxxxxx412&variables=Ravi|482913&to=9876543210

Send an OTP

Create the code in your application, send it with your approved OTP template, and check it in your own system when the user types it. Keep OTP templates on a service route, so they reach numbers on the DND list.

$otp = (string) random_int(100000, 999999);
// Save a hash of $otp with the user's number and an expiry time.

$ch = curl_init('https://cloudsms.in/api/v1/sms/send');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer YOUR_API_KEY', 'Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode([
        'template_id' => 'YOUR_OTP_TEMPLATE_ID',
        'variables'   => [$otp],
        'to'          => [$mobile],
    ]),
]);
$result = json_decode(curl_exec($ch), true);

Different values per number

To send each number its own values, such as a name and an amount, use messages instead of to and variables.

POST /sms/send
{
  "template_id": "1207168xxxxxxxx412",
  "messages": [
    { "to": "9876543210", "variables": ["Ravi", "1,200"] },
    { "to": "9123456780", "variables": ["Sita", "860"] }
  ]
}

Send full text

If you prefer to send the final message yourself, pass message with the exact approved text and the DLT template_id. The sender ID must be added to your account in the panel. entity_id is optional; the sender's entity is used by default.

POST /sms/send
{
  "sender": "ACMEIN",
  "template_id": "1207168xxxxxxxx412",
  "message": "Dear Ravi, 482913 is your login OTP for Acme. Do not share it.",
  "to": "9876543210"
}

The text must match the registered template exactly, or the operator blocks the message.

Check balance

GET /balance

Response
{
  "success": true,
  "balance": 1000.00,
  "sms_credits": 6666,
  "rate": 0.15,
  "currency": "INR"
}

rate is your price per SMS credit in rupees, without GST.

List templates

GET /templates returns your sender IDs and templates with the number of values each one needs.

Response
{
  "success": true,
  "templates": [
    {
      "template_id": "1207168xxxxxxxx412",
      "name": "Login OTP",
      "sender": "ACMEIN",
      "entity_id": "1201160xxxxxxxx790",
      "category": "service_implicit",
      "variables": 2,
      "content": "Dear {#var#}, {#var#} is your login OTP for Acme. Do not share it.",
      "usable": true
    }
  ]
}

Delivery reports

GET /reports with one of these parameters:

ParameterReturns
batch_id or request_idThe batch and the status of each number, 1,000 per page (page).
mobileThe last 100 messages sent to one number.
from, toYour batches in a date range, for example from=2026-10-01&to=2026-10-07.
GET /reports?batch_id=1042
{
  "success": true,
  "batch": { "batch_id": 1042, "status": "submitted", "numbers": 2, "...": "..." },
  "page": 1,
  "pages": 1,
  "numbers": [
    {
      "mobile": "9876543210",
      "status": "delivered",
      "description": "Delivered successfully",
      "sms_parts": 1,
      "sent_at": "2026-10-22T10:15:02+05:30",
      "delivered_at": "2026-10-22T10:15:05+05:30",
      "request_id": "SLBZPIxP4mRVdf4"
    }
  ]
}

Number status is one of sent (waiting for the operator), delivered, failed, undelivered, rejected, expired or dnd.

Webhooks

Add your URL in the panel under API and webhooks. When delivery reports arrive, we POST them to your URL in groups of up to 200.

Body
{
  "events": [
    {
      "event": "sms.status",
      "batch_id": 1042,
      "sender": "ACMEIN",
      "template_id": "1207168xxxxxxxx412",
      "mobile": "9876543210",
      "status": "delivered",
      "description": "Delivered successfully",
      "sms_parts": 1,
      "sent_at": "2026-10-22T10:15:02+05:30",
      "delivered_at": "2026-10-22T10:15:05+05:30",
      "request_id": "SLBZPIxP4mRVdf4"
    }
  ]
}

Each request has two headers. X-CloudSMS-Timestamp is the Unix time, and X-CloudSMS-Signature is sha256= followed by the HMAC-SHA256 of timestamp + "." + body, keyed with your signing secret. Check it before trusting the data, and reply with any 2xx status. Failed deliveries are retried every minute, up to 5 times.

$body = file_get_contents('php://input');
$ts   = $_SERVER['HTTP_X_CLOUDSMS_TIMESTAMP'] ?? '';
$sig  = $_SERVER['HTTP_X_CLOUDSMS_SIGNATURE'] ?? '';
$good = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, 'YOUR_SIGNING_SECRET');

if (!hash_equals($good, $sig) || abs(time() - (int) $ts) > 300) {
    http_response_code(401);
    exit;
}
foreach (json_decode($body, true)['events'] as $event) {
    // update your records with $event['mobile'] and $event['status']
}
http_response_code(200);

Errors

Errors use a 4xx or 5xx status and this body:

{
  "success": false,
  "error": {
    "code": "INSUFFICIENT_CREDITS",
    "message": "This needs 600 SMS credits; the account has 555."
  }
}
HTTPCodeMeaning
400INVALID_JSONThe body is not valid JSON.
401UNAUTHORIZEDMissing or wrong API key.
402INSUFFICIENT_CREDITSNot enough SMS credits. Add balance.
403IP_NOT_ALLOWEDThe request came from an IP address not on your list.
403ACCOUNT_NOT_ACTIVEThe account is waiting for approval or disabled.
404TEMPLATE_NOT_FOUNDNo active template with this template_id in your account.
404NOT_FOUNDNo batch for this batch_id or request_id.
405METHOD_NOT_ALLOWEDWrong HTTP method for the endpoint.
422INVALID_REQUESTA required field is missing or wrong.
422INVALID_VARIABLESA value is missing, empty or longer than 30 characters.
422INVALID_NUMBERSNo valid mobile number in the request.
422SENDER_NOT_ALLOWEDThe sender ID is not in your account, or does not match the template.
422TOO_MANY_NUMBERSMore numbers than allowed in one request.
422INVALID_SCHEDULEschedule_at is in the past or too far ahead.
429RATE_LIMITEDToo many requests in a minute.
502GATEWAY_ERRORThe operator network refused the message. The message explains why.

Limits and SMS length

  • 300 requests a minute per API key.
  • Up to 1,00,000 numbers per request.
  • Each value is up to 30 characters, a DLT rule.
  • One SMS credit covers 160 characters in English, or 70 in Unicode (Hindi, Bengali and other scripts). Longer messages are split into parts of 153 or 67 characters, and each part uses one credit.

Questions about the API? Write to support@cloudsms.in.