API Documentation
The Sahla SMS API is a plain REST API over HTTPS. Every request is authenticated with an API key and returns JSON. Base URL:
https://api.sahlasms.dev/api/v1
Quick start
Pair an Android device in your dashboard, create an API key, then send your first message. The device ID is optional — Sahla SMS picks an online device automatically when you omit it.
1curl -X POST https://api.sahlasms.dev/api/v1/gateway/send-sms \2 -H "Content-Type: application/json" \3 -H "x-api-key: $SAHLA_API_KEY" \4 -d '{5 "recipients": ["+1234567890"],6 "message": "Hello from Sahla SMS!"7 }'Authentication
Pass your API key in the x-api-key header on every request. Keys are shown once at creation — store them in your server environment and never expose them in client-side code.
x-api-key: sk_live_3f9a...c21dKeys can be scoped to sms:send, sms:read, devices:read, and webhooks:manage. Revoking a key takes effect immediately.
Send a single SMS
POST/gateway/send-sms
1{2 "recipients": ["+1234567890"],3 "message": "Your verification code is 481932",4 "deviceId": "dev_8c21f0" // optional5}1{2 "success": true,3 "data": {4 "messageId": "msg_7b12ce",5 "status": "queued",6 "segments": 1,7 "recipients": 1,8 "queuedAt": "2026-01-14T09:21:44Z"9 }10}Send bulk SMS
POST/gateway/send-bulk-sms
Pass up to 1,000 recipients per request, or a saved contact group. Messages are queued and sent steadily by your device with a short pause between each.
1{2 "recipients": ["+1234567890", "+212612345678"],3 "groupId": "grp_4a91bd", // optional, merged with recipients4 "message": "Hi {{name}}, your order has shipped.",5 "variables": { "name": "Sam" }6}1{2 "success": true,3 "data": {4 "batchId": "bat_19fd03",5 "queued": 2,6 "rejected": 0,7 "estimatedCompletion": "2026-01-14T09:24:10Z"8 }9}List devices
GET/gateway/devices
1{2 "success": true,3 "data": [4 {5 "id": "dev_8c21f0",6 "name": "Pixel 6a",7 "phoneNumber": "+1234567890",8 "status": "online",9 "batteryLevel": 87,10 "signalStrength": 4,11 "lastSeenAt": "2026-01-14T09:21:02Z",12 "messagesSent": 184213 }14 ]15}Message status
GET/gateway/messages/:id
A message moves through queued → sent → delivered. A failure returns failed with an errorReason.
1{2 "success": true,3 "data": {4 "id": "msg_7b12ce",5 "recipient": "+1234567890",6 "status": "delivered",7 "segments": 1,8 "queuedAt": "2026-01-14T09:21:44Z",9 "sentAt": "2026-01-14T09:21:47Z",10 "deliveredAt": "2026-01-14T09:21:52Z"11 }12}Receive SMS
GET/gateway/messages?direction=inbound
Enable SMS receiving in the Android app, then poll inbound messages or — better — subscribe to the sms.received webhook event for real-time delivery.
Webhooks
Register a webhook URL in the dashboard and subscribe to sms.sent, sms.delivered, sms.failed, sms.received, and device.status. Every request carries an x-sahla-signature header: an HMAC-SHA256 of the raw body using your signing secret.
1{2 "event": "sms.delivered",3 "timestamp": "2026-01-14T09:21:52Z",4 "data": {5 "messageId": "msg_7b12ce",6 "deviceId": "dev_8c21f0",7 "recipient": "+1234567890",8 "status": "delivered"9 }10}1import { createHmac, timingSafeEqual } from 'crypto'2 3const expected = createHmac('sha256', process.env.SIGNING_SECRET)4 .update(rawBody)5 .digest('hex')6 7const ok = timingSafeEqual(8 Buffer.from(signatureHeader),9 Buffer.from(expected),10)Failed deliveries are retried with exponential backoff for up to 24 hours. Respond with any 2xx within 10 seconds to acknowledge.
Errors & rate limits
Errors return a non-2xx status with a JSON body. The API allows 60 requests per minute per key; exceeding it returns 429 with a Retry-After header.
1{2 "success": false,3 "error": {4 "code": "invalid_recipient",5 "message": "Recipient +12345 is not a valid E.164 phone number"6 }7}401 unauthorized— missing, revoked, or malformed API key403 insufficient_scope— the key lacks the required scope409 no_device_available— no paired device is currently online422 quota_exceeded— monthly plan quota reached
