Android SMS gateway API
SMS Gora sends and receives SMS through Android phones you link to your Tekservix account. Your software calls this REST API; the phone sends the text from its own SIM card and reports back sent and delivered status. Incoming SMS reach you by webhook.
Base URL https://tekservix.com/smsgora/api/v1
Format JSON (UTF-8) in and out
Auth x-api-key: <key> (or Authorization: Bearer <key>)
Set up a phone
- On an Android phone (7.0 or newer) with a SIM card, download the SMS Gora app and install it.
- In the dashboard, press Add phone. A QR code and an 8-character code appear.
- In the app, tap Scan QR code (or Enter code) and allow the SMS permissions. The phone shows Online.
- Note the phone’s ID (shown on its card) and create an API key under API keys.
Authentication
API keys start with sgk_ and work for every phone in your account. Send the key in the x-api-key header — Authorization: Bearer sgk_… works too.
curl https://tekservix.com/smsgora/api/v1/devices -H "x-api-key: sgk_your_key"
Responses & errors
Every response has success. Successful calls return data; failures return message and, for invalid fields, errors.
{ "success": true, "message": "SMS added to the queue", "data": { "batch_id": null, "message_ids": [1873], "status": "queued", … } }
{ "success": false, "message": "\"recipients\" is required: a phone number or a list of numbers.", "errors": { "recipients": "required" } }
| HTTP | Meaning |
|---|---|
| 400 / 422 | Invalid request — check message and errors. |
| 401 | Missing or wrong API key. |
| 402 | No active plan, or the phone isn’t covered by your plan (upgrade or remove a phone). |
| 403 | The account or phone is suspended. |
| 404 | Unknown endpoint, phone or message. |
| 409 | The phone isn’t paired, or sending is switched off for it, or the action doesn’t fit the message’s state. |
| 429 | Rate limit or your plan’s daily/monthly SMS limit reached — see message and Retry-After. |
Limits & delivery
- Requests: 300 per minute per key (
X-RateLimit-Remainingheader). - Recipients: up to 1,000 per
send-smscall, 5,000 per bulk call. Use campaigns for bigger lists. - Message length: up to 1,600 characters. Long texts are sent as a multi-part SMS (160 GSM characters per SMS, or 70 with emoji and special letters).
- Plans: every plan has unlimited SMS; plans differ by the number of phones. Without an active plan, sending answers
402and queued messages wait until a plan is active. - Pace: each phone waits 2.5 s between texts by default (change it per phone) so operators don’t flag the SIM.
- Offline phones: messages wait in the queue and go out when the phone reconnects. A message the phone took but never confirmed is handed out again after 10 minutes (up to 3 times).
Send SMS
/v1/devices/{deviceId}/send-sms| Field | Type | Description |
|---|---|---|
recipients | array or string | Phone numbers, ideally in international format (+250788123456). A comma-separated string works too. Also accepted as to. |
message | string | The text, up to 1,600 characters. Also accepted as text. |
sim_slot | integer, optional | 0 = SIM 1, 1 = SIM 2. Leave out to use the phone’s default SIM. |
schedule_at | ISO 8601, optional | Send later, e.g. 2026-10-01T08:00:00Z (up to a year ahead). |
curl -X POST https://tekservix.com/smsgora/api/v1/devices/42/send-sms \
-H "x-api-key: sgk_your_key" -H "Content-Type: application/json" \
-d '{"recipients": ["+250788123456", "+250722000000"], "message": "Your appointment is tomorrow at 10:00."}'
{ "success": true, "message": "SMS added to the queue",
"data": { "batch_id": 311, "smsBatchId": 311, "message_ids": [1873, 1874], "recipientCount": 2,
"status": "queued", "device_id": 42, "device_online": true, "invalid_recipients": [] } }
Node.js
const res = await fetch('https://tekservix.com/smsgora/api/v1/devices/42/send-sms', {
method: 'POST',
headers: { 'x-api-key': process.env.SMSGORA_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ recipients: ['+250788123456'], message: 'Your code is 482913' }),
});
console.log(await res.json());
PHP
$ch = curl_init('https://tekservix.com/smsgora/api/v1/devices/42/send-sms');
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['x-api-key: ' . getenv('SMSGORA_KEY'), 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode(['recipients' => ['+250788123456'], 'message' => 'Your code is 482913']),
]);
echo curl_exec($ch);
Python
import os, requests
r = requests.post('https://tekservix.com/smsgora/api/v1/devices/42/send-sms',
headers={'x-api-key': os.environ['SMSGORA_KEY']},
json={'recipients': ['+250788123456'], 'message': 'Your code is 482913'})
print(r.json())
Send without a phone ID
/v1/send-smsSame body as above. SMS Gora picks your best phone: online, sending switched on, covered by your plan, with the shortest queue. Pass device_id to choose one.
Bulk SMS
/v1/devices/{deviceId}/send-bulk-smsDifferent texts for different people in one call. messageTemplate is used for items without their own message.
{ "messageTemplate": "Hello, your parcel is ready for pickup.",
"messages": [
{ "recipients": ["+250788123456"], "message": "Hi Aline, parcel #1042 is ready." },
{ "recipients": ["+250722000000", "+250733000000"] }
] }
Answer: { "batch_ids": [...], "message_ids": [...], "recipientCount": 3 }.
Message statuses
| Status | Meaning |
|---|---|
scheduled | Waiting for schedule_at. |
queued | Waiting for the phone to collect it. |
dispatched | The phone has it and is sending. |
sent | The phone handed it to the operator. |
delivered | The operator confirmed delivery to the recipient’s phone (when the network supports delivery reports). |
failed | Not sent — see error (no signal, no credit, SIM blocked, invalid number…). |
cancelled | Cancelled before the phone collected it. |
received | An incoming SMS. |
List messages
/v1/messagesNewest first. Query: direction (in/out), status, device_id, phone, since (ISO date), after_id, page, limit (≤ 100).
{ "success": true, "data": { "data": [
{ "id": 1874, "device_id": 42, "direction": "out", "to": "+250722000000", "message": "Your appointment…", "status": "delivered",
"error": null, "sim_slot": 0, "parts": 1, "source": "api", "batch_id": 311, "campaign_id": null,
"scheduled_at": null, "sent_at": "2026-10-01T09:30:14Z", "delivered_at": "2026-10-01T09:30:19Z", "received_at": null, "created_at": "2026-10-01T09:30:11Z" }
], "page": 1, "limit": 50, "total": 1 } }
Received SMS
/v1/devices/{deviceId}/get-received-sms/v1/received-smsIncoming texts (for one phone, or all). Same filters and shape as List messages; incoming items have from instead of to. Tip: poll with after_id, or better, use the sms.received webhook.
One message & cancel
/v1/messages/{id}/v1/messages/{id}/cancelCancelling works while the message is queued or scheduled.
Batches
/v1/batches/{id}A send to several recipients creates a batch. Returns total, counts by status and the messages.
Phones
/v1/devices/v1/devices/{id}{ "id": 42, "name": "Office phone", "status": "online", "paired": true, "enabled": true, "receive_enabled": true,
"brand": "samsung", "model": "SM-A145F", "android_version": "14", "app_version": "1.0.0",
"sims": [ { "slot": 0, "carrier": "MTN", "number": "+250788…" }, { "slot": 1, "carrier": "Airtel", "number": "" } ],
"default_sim": null, "battery": 83, "charging": false, "network": "wifi", "last_seen_at": "2026-10-01 09:31:02",
"msgs_sent": 1284, "msgs_failed": 3, "msgs_received": 37, "covered": true }
status is online, offline (no contact for 5 minutes), pending (not paired) or suspended.
Plan & usage
/v1/account{ "plan": "Pro", "devices_allowed": 3, "devices_used": 1, "daily_limit": 0, "monthly_limit": 0,
"usage": { "today": 12, "month": 140 }, "renews_at": "2026-10-29 16:40:12" }
0 means unlimited. plan is null when the account has no active plan.
Webhooks: setup & signature
Add webhooks under Webhooks (up to 10, for all phones or one). We POST JSON to your https URL:
POST /your/webhook
Content-Type: application/json
X-SmsGora-Event: sms.received
X-SmsGora-Delivery: 99812
X-SmsGora-Signature: sha256=5d3c…
{ "event": "sms.received", "timestamp": "2026-10-01T09:30:12Z", "device_id": 42,
"data": { "id": 1875, "device_id": 42, "direction": "in", "from": "+250788123456",
"message": "YES, confirm my booking", "status": "received", "sim_slot": 0,
"received_at": "2026-10-01T09:30:10Z", … } }
Check the signature: compute HMAC-SHA256 of the raw request body with your webhook’s secret and compare it with the header (after sha256=). Reply with any 2xx within 10 seconds. Failed deliveries are retried after 1 min, 5 min, 30 min, 2 h and 6 h.
// Node.js (Express)
app.post('/sms/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = 'sha256=' + crypto.createHmac('sha256', process.env.SMSGORA_SECRET).update(req.body).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(req.get('X-SmsGora-Signature') || ''))) return res.sendStatus(401);
const e = JSON.parse(req.body);
if (e.event === 'sms.received') console.log(e.data.from, e.data.message);
res.sendStatus(200);
});
// PHP
$raw = file_get_contents('php://input');
$ok = hash_equals('sha256=' . hash_hmac('sha256', $raw, getenv('SMSGORA_SECRET')), $_SERVER['HTTP_X_SMSGORA_SIGNATURE'] ?? '');
if (!$ok) { http_response_code(401); exit; }
$e = json_decode($raw, true);
Events
| Event | When | data |
|---|---|---|
sms.received | An SMS arrives on a phone. | The message (from, message, sim_slot, received_at…) |
sms.sent | The phone handed the SMS to the operator. | The message |
sms.delivered | The operator confirmed delivery. | The message |
sms.failed | The SMS couldn’t be sent. | The message, with error |
device.online | A phone paired or came back online. | { device_id, name } |
device.offline | No contact from a phone for 5 minutes. | { device_id, name } |
Manage webhooks by API
/v1/webhooks/v1/webhooks/v1/webhooks/{id}POST /v1/webhooks
{ "url": "https://your-app.com/sms/webhook", "events": ["sms.received", "sms.failed"], "device_id": null }
→ { "id": 7, "url": "…", "events": [...], "secret": "3f9c…" } (the secret is only returned here)
TextBee compatibility
SMS Gora accepts TextBee-style requests, so most integrations switch by changing the base URL and the key:
| TextBee | SMS Gora |
|---|---|
https://api.textbee.dev/api/v1 | https://tekservix.com/smsgora/api/v1 |
POST /gateway/devices/{id}/send-sms {recipients, message} | Same path and body (the /gateway prefix is optional) |
POST /gateway/devices/{id}/send-bulk-sms | Same path and body |
GET /gateway/devices/{id}/get-received-sms | Same path |
x-api-key header | Same header |
Answer data.smsBatchId | Also returned (with message_ids and batch_id) |
Webhook payloads differ: see Events.