API
Human reference for /api/v1. Machine-readable copies are on this same site.
Create a token under Users. The secret is shown once. Send it as a bearer token. That is the method to use:
Authorization: Bearer sms_…
X-Api-Token: sms_… is the same secret if you cannot set Authorization. You do not need both.
Base URL: https://sendsms.top. A token only reads and sends for its own company. A Super token can allocate credits to another companyId, but message lists stay on the token's company.
401 unauthorized means the token is missing, unknown, or revoked. 403 forbidden means the role cannot call that route. 402 insufficient_wallet means the company has no credits and the message was not sent. 409 opted_out means that number sent an opt-out and was not contacted. 409 idempotency_mismatch means the Idempotency-Key was reused with a different body. 429 rate_limited means more than 60 requests in a minute for that token. The counter is stored in the database, so a restart does not clear it. Message JSON is camelCase. A delivery or inbound reply is also POSTed to the callback URL, with X-SendSMS-Signature: sha256=... over the raw body.
One accepted send costs one credit, not one credit per segment. GSM-7 text is 160 characters, or 153 per segment when longer. Unicode and emoji are UCS-2: 70 characters, or 67 per segment. The maximum is 10 segments. status is our word (sent, delivered, failed). dlrStatus is the carrier code (DELIVRD means delivered). There is no scheduled send.
OpenAPI is at https://sendsms.top/api/openapi.json. The Postman collection (v2.1) is at https://sendsms.top/api/postman.json. Import that URL in Postman and set the token variable.
Roles
| Role | Can |
|---|---|
| View Only | Read wallet, customers, sent SMS, inbox, one message, and the company audit log. |
| Normal | View Only, plus send SMS. |
| Admin | Normal, plus list users. |
| Super | Admin, plus allocate wallet credits and read the platform audit log. |
GET /api/v1/me
Who this token is. Returns the token name, role, company, and which actions that role is allowed to call.
Access: Any token
Response
{
"ok": true,
"name": "Billing",
"role": "normal",
"roleLabel": "Normal",
"companyId": "00000000-0000-0000-0000-000000000000",
"companyName": "Acme",
"permissions": {
"sendSms": true,
"manageUsers": false,
"viewWallet": true,
"allocateWallet": false,
"settings": false
}
}
GET /api/v1/wallet
Wallet balance. Integer credit balance for the token's company. One accepted outbound SMS costs one credit.
Access: Any token
Response
{
"ok": true,
"companyId": "00000000-0000-0000-0000-000000000000",
"balance": 120
}
POST /api/v1/wallet/allocate
Allocate credits. Adds a positive integer number of credits to a company wallet. amount must be a positive integer.
Access: Super
Request
{
"companyId": "00000000-0000-0000-0000-000000000000",
"amount": 100,
"reason": "October top-up"
}
Response
{
"ok": true,
"balance": 220,
"ledgerId": "00000000-0000-0000-0000-000000000001"
}
GET /api/v1/customers
Customers. Up to 100 customers per page for the token's company, ordered by name. Use page and pageSize. pageSize max is 100.
Access: Any token
Query: page — Page number, starting at 1.; pageSize — Rows per page, 1–100. Default 50.
Response
{
"ok": true,
"page": 1,
"pageSize": 50,
"total": 1,
"customers": [
{
"id": "00000000-0000-0000-0000-000000000002",
"name": "Ada",
"phone_e164": "+27821234567",
"email": "ada@example.com"
}
]
}
GET /api/v1/sms/sent
Sent SMS. Outbound messages for the token's company, 50 per page by default (max 100). status is our word, such as delivered. dlrStatus is the raw carrier code, such as DELIVRD. senderName is the API token when a token sent the message. createdByName is the person who created that token.
Access: Any token
Query: page — Page number, starting at 1.; pageSize — Rows per page, 1–100. Default 50.
Response
{
"ok": true,
"messages": [
{
"id": "00000000-0000-0000-0000-000000000003",
"to": "+27821234567",
"body": "Hello",
"status": "delivered",
"statusLabel": "Delivered",
"dlrStatus": "DELIVRD",
"dlrLabel": "Delivered",
"createdAt": "2026-10-09T12:00:00.000Z",
"threadId": "00000000-0000-0000-0000-000000000003",
"senderName": "Billing",
"createdByName": "Ada"
}
]
}
GET /api/v1/sms/inbox
Inbox. Inbound messages for the token's company, 50 per page by default (max 100). Replies are not billed. unread=1 keeps messages that have not been opened. optOut=1 keeps opt-out replies.
Access: Any token
Query: page — Page number, starting at 1.; pageSize — Rows per page, 1–100. Default 50.; unread — 1 to only include unread replies.; optOut — 1 to only include opt-out replies.
Response
{
"ok": true,
"messages": [
{
"id": "00000000-0000-0000-0000-000000000004",
"from": "+27821234567",
"body": "Thanks",
"status": "received",
"createdAt": "2026-10-09T12:05:00.000Z",
"threadId": "00000000-0000-0000-0000-000000000003",
"optOut": false,
"unread": true
}
]
}
POST /api/v1/sms
Send one SMS. Sends one message to one number. to is E.164 (+2782…) or a South African local number (082…). Optional senderId uses a registered SMSPortal sender; omit it for the account default. The response uses senderId and leaves from empty rather than naming the upstream provider. Optional clientReference is your own id, stored on the message and returned as clientReference. Optional sendAt is an ISO time, at least 15 seconds ahead and at most 30 days, to send later. The credit is taken when the message is actually sent, still one credit per message rather than per segment. Send Idempotency-Key to make a retry return the first result instead of sending twice. A View Only token receives 403. An empty wallet receives 402 and the provider is not called. An opted-out number receives 409 opted_out. GSM-7 is 160 characters (153 if concatenated). Unicode, including emoji, is UCS-2: 70 characters (67 if concatenated). Maximum 10 segments.
Access: Normal, Admin, or Super
Request
{
"to": "+27821234567",
"body": "Hello from SendSMS",
"senderId": "ACME",
"clientReference": "order-19"
}
Response
{
"ok": true,
"id": "00000000-0000-0000-0000-000000000005",
"to": "+27821234567",
"status": "sent",
"statusLabel": "Sent to the network",
"dlrStatus": null,
"dlrLabel": null,
"encoding": "GSM-7",
"segments": 1,
"credits": 1,
"clientReference": "order-19",
"senderId": "ACME",
"walletBalance": 119
}
GET /api/v1/sms/{id}
One message. Fetch one message in this company by id, including status, the raw carrier code, and your clientReference. 404 if it is not in the token's company.
Access: Any token
Response
{
"ok": true,
"message": {
"id": "00000000-0000-0000-0000-000000000005",
"status": "delivered",
"statusLabel": "Delivered",
"dlrStatus": "DELIVRD",
"dlrLabel": "Delivered",
"clientReference": "order-19",
"senderId": "ACME",
"senderName": "Billing",
"createdByName": "Ada",
"from": null
}
}
POST /api/v1/sms/bulk
Send the same SMS to several numbers. Same body to 1–100 numbers. One credit per accepted number, taken when that number is sent. Opted-out numbers are skipped. Optional sendAt schedules every number in the batch.
Access: Normal, Admin, or Super
Request
{
"to": [
"+27821234567",
"+27827654321"
],
"body": "Hello from SendSMS",
"clientReference": "batch-1"
}
Response
{
"ok": true,
"sent": 2,
"encoding": "GSM-7",
"segments": 1,
"creditsEach": 1,
"results": []
}
GET /api/v1/callback
Read the delivery callback. The https URL that receives delivery reports and inbound replies for this company. The secret is not returned.
Access: Normal, Admin, or Super
Response
{
"ok": true,
"configured": true,
"url": "https://hooks.example.com/sms"
}
PUT /api/v1/callback
Set the delivery callback. Save an https URL and a secret of 16–128 characters. SendSMS POSTs { event, message } when a delivery report or inbound reply arrives. event is delivery or inbound. message is the same camelCase object as GET /api/v1/sms/{id}. Header X-SendSMS-Signature is sha256= plus the HMAC-SHA256 of the raw body using the secret. Private and loopback addresses are rejected. A failed callback does not change the message.
Access: Normal, Admin, or Super
Request
{
"url": "https://hooks.example.com/sms",
"secret": "replace-with-a-long-secret"
}
Response
{
"ok": true,
"configured": true,
"url": "https://hooks.example.com/sms"
}
DELETE /api/v1/callback
Remove the delivery callback. Stop delivery and inbound callbacks for this company.
Access: Normal, Admin, or Super
Response
{
"ok": true,
"configured": false
}
GET /api/v1/users
Users. Staff the token is allowed to see. A company admin sees one company. A super token sees every company. Other roles receive 403.
Access: Admin or Super
Response
{
"ok": true,
"users": [
{
"uid": "00000000-0000-0000-0000-000000000006",
"email": "ada@example.com",
"displayName": "Ada",
"role": "company_admin",
"status": "active",
"companyId": "00000000-0000-0000-0000-000000000000",
"companyName": "Acme"
}
]
}
GET /api/v1/audit
Audit log. Company audit events, 50 per page. scope=platform is the platform-wide log and is Super only.
Access: Any token; platform scope is Super
Query: page — Page number, starting at 1.; scope — Omit for the token's company. platform is Super only.
Response
{
"ok": true,
"events": [],
"total": 0,
"page": 1,
"pageSize": 50,
"source": "neon"
}