Developers

Bulk SMS API documentation

The SendBulkSMS REST API sends SMS to 10,000 numbers per request, returns a campaign ID at once and reports every message to your webhook. It speaks JSON over HTTPS, uses a Bearer API key and charges the same per-message rates as the console.

https://sendbulksms.cc/api/v1

Quick start

  1. Create an account and top up your balance in the console.
  2. Open API keys and create a key. Keys can be created after the first top-up is credited. The key is shown once; store it as a secret.
  3. Send a POST request to /sms with the numbers and the text. Read the returned campaign ID, then follow delivery in the console or through webhooks.

Authentication

Every request except the public rate list carries your key in the Authorization header. Keys start with sbk_live_. A new key replaces the old one immediately.

Authorization: Bearer sbk_live_YOUR_KEY

Send SMS

POST/sms

Creates a campaign, charges it from the balance and queues every message. Numbers are cleaned and de-duplicated; those that cannot be used are returned in dropped with a reason and are not charged.

FieldDescription
torequiredOne number or an array of up to 10,000 numbers in international format, for example "+447700900461"
textrequiredMessage text, up to 10 segments. GSM-7 text fits 160 characters per segment, UCS-2 fits 70
schedule_atoptionalISO 8601 time from 5 minutes to 60 days ahead. Omit to send now
client_refoptionalYour own reference, up to 64 of a-z A-Z 0-9 _ . : -, returned in reports and webhooks
opt_outoptionaltrue appends the opt-out line set in your account settings
curl -X POST https://sendbulksms.cc/api/v1/sms \
  -H "Authorization: Bearer sbk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": ["+447700900461", "+5511955550123"],
    "text": "Hi, your order 4821 ships today.",
    "client_ref": "orders-4821"
  }'

Response: the campaign with its cost and the first 1,000 messages. Fetch more with campaign messages.

HTTP/1.1 201 Created
{
  "id": "CP-7KQ2-M9XA-4HTD",
  "status": "sending",
  "source": "api",
  "recipients": 2,
  "segments": 1,
  "encoding": "GSM-7",
  "cost": "0.01078",
  "refunded": "0.00000",
  "client_ref": "orders-4821",
  "scheduled_at": null,
  "created_at": "2026-11-07T10:32:04.118Z",
  "done_at": null,
  "counts": { "queued": 2, "sent": 0, "delivered": 0, "failed": 0, "canceled": 0 },
  "dropped": [],
  "balance": "248.30422",
  "messages": [
    { "id": "SM-2RBW-8MZN-QF6K", "campaign_id": "CP-7KQ2-M9XA-4HTD", "to": "+447700900461", "country": "GB", "parts": 1, "price": "0.00819", "status": "queued", "error": null, "created_at": "2026-11-07T10:32:04.118Z", "sent_at": null, "done_at": null }
  ]
}

Estimate a send

POST/estimate

Takes the same body as /sms and returns the recipients, segments, encoding, total cost and the cost per country without charging or sending anything.

{ "recipients": 2, "dropped": [], "segments": 1, "encoding": "GSM-7", "cost": "0.01078", "balance": "248.31500",
  "countries": [ { "country": "GB", "recipients": 1, "cost": "0.00819" }, { "country": "BR", "recipients": 1, "cost": "0.00259" } ] }

Message status

GET/sms/{message_id}

Returns one message: number, country, segments, price, status, the error for failed messages and the time of each step.

Campaigns

GET/campaigns/{campaign_id}

The campaign with its cost, refunds and a count of messages per status.

GET/campaigns/{campaign_id}/messages?offset=0&limit=100

Messages in the order they were queued, up to 1,000 per page.

POST/campaigns/{campaign_id}/cancel

Cancels a scheduled campaign that has not started and refunds its full cost to the balance.

Balance and rates

GET/balance

{ "balance": "248.31500", "currency": "USD" }

GET/rates

Public, no key needed. Every country with its calling code and price per segment. /rates/GB returns one country.

{ "updated": "2026-11-07", "rates": [ { "country": "GB", "name": "United Kingdom", "calling_code": "+44", "price": "0.00819", "currency": "USD", "unit": "SMS segment" } ] }

Delivery webhooks

Set a webhook URL in the console under API keys. When a message reaches a final status (delivered or failed) we POST a report to it. Answer with any 2xx status within 8 seconds; otherwise the call is retried up to 8 times over about 11 hours. Use id to ignore a report you have already processed.

POST https://your-server.example/sms-status
Content-Type: application/json
X-SendBulkSMS-Signature: t=1794047524,v1=5f0c3b9e...

{
  "event": "message.status",
  "id": "evt_8c1d2e4f6a7b9c0d1e2f",
  "created_at": "2026-11-07T10:32:09.004Z",
  "data": {
    "message_id": "SM-2RBW-8MZN-QF6K",
    "campaign_id": "CP-7KQ2-M9XA-4HTD",
    "to": "+447700900461",
    "country": "GB",
    "status": "delivered",
    "error": null,
    "parts": 1,
    "price": "0.00819",
    "client_ref": "orders-4821",
    "done_at": "2026-11-07T10:32:08.912Z"
  }
}

Check the signature before trusting a report: HMAC-SHA256 of t + "." + raw body with your webhook secret, compared in constant time, and reject times older than 5 minutes.

import { createHmac, timingSafeEqual } from "node:crypto";

// raw = the request body exactly as received (string), header = X-SendBulkSMS-Signature
export function verify(raw, header, secret) {
  const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || "");
  if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > 300) return false;
  const want = Buffer.from(createHmac("sha256", secret).update(m[1] + "." + raw).digest("hex"));
  const got = Buffer.from(m[2]);
  return want.length === got.length && timingSafeEqual(want, got);
}

Statuses

StatusMeaning
queuedAccepted and charged, waiting its turn in the queue
sentHanded to the route, waiting for the delivery report
deliveredThe network confirmed delivery to the handset
failedNot delivered; the price is refunded and error holds the reason
canceledPart of a scheduled campaign canceled before it started; refunded

Errors

Errors use HTTP status codes and a JSON body: { "error": { "code": "insufficient_balance", "message": "..." } }. A request that returns an error never charges the balance.

HTTPCode
400bad_requestA field is missing or not valid; the message explains which
400no_valid_numbersNone of the numbers in "to" could be used
401unauthorizedThe API key is missing or not valid
402insufficient_balanceThe balance does not cover the send; nothing was charged
404not_foundThe campaign, message or endpoint does not exist in your account
409not_scheduledOnly a scheduled campaign that has not started can be canceled
413too_many_recipientsMore than 10,000 numbers in one request
429rate_limitedMore than 300 requests in a minute for this key
500server_errorTry again; no charge is made for a request that fails

Limits

  • 300 requests a minute per API key.
  • 10,000 numbers per send request; 10 segments per message.
  • Messages leave at up to 200 a minute per queue, in the order they were accepted.
  • Scheduling from 5 minutes to 60 days ahead.
  • Numbers on your do-not-send list are skipped and returned in dropped with code suppressed; they are not charged.
Bulk SMS API Documentation: Send SMS with a REST API | SendBulkSMS