SMS API documentation for sending from your own code

A small REST API on the same account, prices and credit as the workspace: send a message or a batch of up to 5,000 numbers, estimate the cost first, follow each message and get delivery events at your webhook URL.

Base URL: https://sendsmsonline.cc/api/v1

Quick start

  1. 1. Create a key. In the workspace open API and webhooks. Keys open after your first credited top-up; each key is shown once, so copy it or save it as a text file.
  2. 2. Check the cost. POST the same body to /costs to see recipients, segments and the total without sending.
  3. 3. Send. POST it to /sends. The answer has the send id; messages leave at once or at send_at.
curl https://sendsmsonline.cc/api/v1/sends \
  -H "Authorization: Bearer $SSO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "recipients": [ { "to": "+447700900461", "fields": { "first_name": "Tom" } }, { "to": "+12015550142", "fields": { "first_name": "Ava" } } ], "text": "Hi {{first_name}}, your table is ready at 7:30.", "name": "Friday bookings", "client_ref": "bookings-1107" }'

Authentication

Send your key in the Authorization header: Authorization: Bearer sso_live_.... Keys are 49 characters and start with sso_live_. Up to 5 keys per account; revoke one in the workspace and it stops working at once. The price list is public and needs no key.

Errors

Errors use HTTP status codes and a JSON body with a stable code and a sentence you can show to a person. Nothing is charged when a request fails.

HTTP/1.1 402 Payment Required
{ "error": { "code": "insufficient_credit", "message": "Short by $3.20. Add funds to send.", "total": "8.62480", "credit": "5.42000" } }
FieldMeaning
400The request needs a change.
401Missing, wrong or revoked key.
402Not enough credit for the total.
404No such send, message, country or endpoint.
409The send has already started.
429More than 240 requests in a minute.

Limits

  • 240 requests a minute per key, shown in X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (unix seconds).
  • 5,000 numbers per request, 2,000 characters per text, 10 segments per message.
  • Messages leave at up to 300 a minute per account.
  • send_at from 10 minutes to 30 days ahead.

Prices

GET/api/v1/prices

GET/api/v1/prices/{country}

The price per message (one segment) for every country, in USD with 5 decimals. No key needed.

{ "price": { "country": "GB", "name": "United Kingdom", "dial_code": "+44", "price_usd": "0.00868" }, "updated": "2026-11-07" }

Account credit

GET/api/v1/account

{ "account": "SO8K2M4Q7X9TBA", "credit_usd": "41.27660", "currency": "USD" }

Estimate a cost

POST/api/v1/costs

Takes the same body as a send and returns what it would cost, without sending or charging. Each recipient's own merged text decides its segments, so the total is exact.

{
  "recipients": 2, "segments_min": 1, "segments_max": 1, "encoding": "gsm7", "messages_billed": 2,
  "total_usd": "0.01426", "credit_usd": "41.27660", "enough_credit": true,
  "countries": [ { "country": "GB", "recipients": 1, "price_usd": "0.00868", "subtotal_usd": "0.00868" },
                 { "country": "US", "recipients": 1, "price_usd": "0.00558", "subtotal_usd": "0.00558" } ],
  "dropped": [], "too_long": null
}

Create a send

POST/api/v1/sends

One text to one or many numbers. Credit for the whole send is taken when it is accepted; failed messages are refunded one by one.

FieldMeaning
toNumbers in international format (+447700900461). Use this or recipients.
recipientsObjects with to and fields, for merge fields: { "to": "+1...", "fields": { "first_name": "Ava" } }.
textThe message, up to 2,000 characters. {{field}} is replaced per recipient; missing values become empty.
send_atOptional ISO 8601 time with offset or Z. Without it the send starts at once.
nameOptional label shown in the workspace (80 characters).
client_refOptional reference returned in webhooks (64 characters).
opt_outAdds the opt-out line saved in Settings to the end of the text.
default_countryISO code used for numbers written without a country code.
HTTP/1.1 201 Created
{
  "send": { "id": "CA7Q29XMAB4DFK", "status": "queued", "source": "api", "recipients": 2, "segments_max": 1,
            "encoding": "gsm7", "cost_usd": "0.01426", "refunded_usd": "0.00000", "client_ref": "bookings-1107",
            "send_at": null, "created_at": "2026-11-07T10:12:03.120Z", "started_at": null, "finished_at": null },
  "dropped": [ { "value": "+4412", "code": "too_short", "message": "Too short" } ]
}

Numbers that cannot be sent are listed in dropped and never charged. Codes: no_country_code, too_short, too_long, not_a_number, wrong_length, not_covered, duplicate and suppressed (on your do-not-send list).

Get or list sends

GET/api/v1/sends/{id}

GET/api/v1/sends?limit=25&before={id}

A single send includes counts per message status. Lists are newest first; pass next_before as before for the next page. Sends from the workspace appear here too, with source: "web".

Messages

GET/api/v1/sends/{id}/messages?status=failed&limit=100

GET/api/v1/messages/{id}

{ "message": { "id": "MS3D8RW0K2QJ7N", "send_id": "CA7Q29XMAB4DFK", "to": "+447700900461", "country": "GB",
  "segments": 1, "price_usd": "0.00868", "status": "delivered", "reason": null, "reason_text": null,
  "created_at": "2026-11-07T10:12:03.120Z", "sent_at": "2026-11-07T10:12:04.010Z", "done_at": "2026-11-07T10:12:09.400Z" } }

Cancel a send

POST/api/v1/sends/{id}/cancel

Only scheduled sends that have not started can be canceled. The full cost returns to your credit and the answer includes refunded_usd.

Webhooks

Set an https URL in the workspace (API and webhooks). We POST an event when a message is delivered or fails, for sends from the API and the workspace alike. Answer with any 2xx status within 10 seconds. Failed deliveries are tried again up to 7 times in all: right away, then after 1 minute, 5 minutes, 30 minutes, 1 hour, 2 hours and 3 hours. The workspace shows each attempt with its status and answer.

POST https://your-server.example/sms-events
X-SSO-Event: message.delivered
X-SSO-Delivery: evt_5a0c9e31d2b7f4a68c
X-SSO-Timestamp: 1794132729
X-SSO-Signature: sha256=8f1c...e42a

{ "id": "evt_5a0c9e31d2b7f4a68c", "event": "message.delivered", "created": "2026-11-07T10:12:09.000Z",
  "data": { "message": { "id": "MS3D8RW0K2QJ7N", "send_id": "CA7Q29XMAB4DFK", "to": "+447700900461", "country": "GB",
  "segments": 1, "price_usd": "0.00868", "status": "delivered", "reason": null, "reason_text": null,
  "client_ref": "bookings-1107", "created_at": "...", "sent_at": "...", "done_at": "..." } } }

The signature is HMAC-SHA256 of timestamp + "." + raw body with your signing secret (it starts with sso_whk_). Check it on the raw body before parsing, reject timestamps older than 5 minutes, and use the delivery id to ignore repeats.

import crypto from "node:crypto";

// rawBody: the request body exactly as received (a string), before JSON.parse
export function verify(rawBody, headers, secret) {
  const ts = headers["x-sso-timestamp"];
  const sig = headers["x-sso-signature"] || "";
  const want = "sha256=" + crypto.createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(ts)) < 300;
  return fresh && sig.length === want.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(want));
}

Events: message.delivered, message.failed, and test from the Send test event button.

Statuses and reasons

FieldMeaning
queuedWaiting to leave our queue.
sentHanded to the network, waiting for the delivery report. Most reports arrive within minutes; some networks take up to 24 hours.
deliveredThe network confirmed the phone received it.
failedNot delivered. The price was refunded to your credit.
canceledNot sent because you canceled. Refunded.

Failure reasons (the reason field):

FieldMeaning
network_errorNetwork error
absentPhone switched off or out of coverage
not_in_serviceNumber not in service
blockedBlocked by the network or the recipient
landlineLandline or number that cannot receive SMS
content_filterRejected by the network's content filter
expiredExpired: no report before the network's time limit
canceledCanceled before sending
unknownUnknown network response

Service checks are on the status page and API changes in the changelog.

Get your API key

Create an account, add funds once and open API and webhooks in the workspace. The same key works for every country.

Create free account
SMS API Documentation: Send Bulk SMS from Your Code | SendSMSOnline