WilCybertek SMS Gateway
A direct, low-latency SMS platform connecting your applications to Zambia's three major mobile networks — MTN, Airtel, and Zamtel — through physical, on-premise Android GSM hardware. No international routing, no hidden relay fees. This page documents everything the platform can do: the developer REST API, the Android gateway app, the web dashboard, device management, the two-way messaging inbox, the built-in AI assistant, and wallet top-up.
Creating an Account
Every feature on this page — the API, the dashboard, the AI assistant — sits behind a free developer account.
| Field | Rule |
|---|---|
| name | 2–80 characters |
| Must contain "@" and be at least 5 characters; must be unique | |
| password | At least 8 characters, with at least one uppercase letter and one digit |
Registering with an email that already has an account redirects you to /login instead of failing silently. Forgot your password? /forgot-password emails a recovery link (valid for 1 hour) to reset it — the response message is intentionally identical whether or not the email exists, so the flow can't be used to find out which emails are registered.
Android Gateway App
The WilCybertek Gateway app turns a spare Android phone into a physical SMS relay on MTN, Airtel, or Zamtel. It's the hardware behind every message this platform sends — install it before your first API call or the message never leaves QUEUED status.
Setup
https://sms.wilcybertek.com, then grant the SMS and notification permissions requested.0 = SIM 1, 1 = SIM 2).Quickstart
Send your first SMS in under 5 minutes.
curl -X POST https://sms.wilcybertek.com/api \
-H "Content-Type: application/json" \
-d '{
"api_key": "YOUR_API_KEY_HERE",
"to": "260977000000",
"message": "Hello! This is my first WilCybertek SMS."
}'
Authentication
The platform uses two independent authentication mechanisms depending on whether you're calling the API or using the web app.
API Key (developer / device endpoints)
Every API and device endpoint below resolves your account from an API key. The key can be supplied in any of these ways, checked in this order — the first one found wins:
| Priority | Location | Example |
|---|---|---|
| 1 | X-API-Key | HTTP header (recommended) |
| 2 | api_key | JSON body field |
| 3 | api_key | URL query string |
| 4 | api_key | Form-encoded body field |
X-API-Key: wp_live_a1b2c3d4-e5f6-...
Session Cookie (web dashboard)
Logging in at /login issues a signed session cookie. All dashboard pages (Dashboard, Devices, Messages, Send SMS, AI Assistant) and their POST actions run behind this session — see Security Model for CSRF details. Session-based routes do not accept an API key.
Send SMS
The core endpoint. Queues one SMS for delivery through your default (or a specified) gateway device.
Accepts either application/json or application/x-www-form-urlencoded.
Body Parameters
| Field | Type | Description | |
|---|---|---|---|
| to | string | required | Recipient number. Auto-normalized to 260XXXXXXXXX — see Phone Number Formatting. |
| message | string | required | SMS body. Max 1,600 characters. |
| device_id | string | optional | Send from a specific gateway device instead of your account default. |
| sim_slot | int | optional | 0 = SIM 1, 1 = SIM 2. Falls back to your account/device default. |
POST /api HTTP/1.1
Host: sms.wilcybertek.com
Content-Type: application/json
X-API-Key: wp_live_xxxx-xxxx-...
{
"to": "260977000000",
"message": "Your order #4821 has shipped.",
"sim_slot": 0
}
{
"success": true,
"id": 4821,
"status": "QUEUED"
}
Message Status Values
Every queued message moves through this lifecycle. You'll see these values in your dashboard logs and in /update_status.php reports.
| Status | Meaning |
|---|---|
| QUEUED | Accepted, waiting for a gateway device to pick it up. |
| SENDING | A device has claimed the job and is dispatching it via GSM. |
| SENT | Confirmed sent by the device. |
| DELIVERED | Confirmed delivered (when the network/device reports a delivery receipt). |
| FAILED | Delivery failed — the spent credit is automatically refunded. |
| PROCESSING | Shown on the dashboard for jobs mid-flight; requeue-able if stuck. |
Register a Device
Registers (or updates) a gateway device under your account. The official Android app calls this automatically — you only need it directly if you're building a custom gateway client.
| Field | Type | Description | |
|---|---|---|---|
| device_id | string | required | Stable unique identifier for the phone (e.g. Android ID). |
| device_name | string | optional | Friendly label shown on the Devices page. |
| sim_slots | int | optional | Number of active SIM slots. Defaults to 1. |
{ "success": true }
Push Token Registration
Registers a device's push (Firebase Cloud Messaging) token so it can be woken instantly when a new job is queued, instead of waiting for its next poll.
| Field | Type | Description | |
|---|---|---|---|
| device_id | string | required | Must match a registered device. |
| fcm_token | string | required | Push token issued by Firebase on the device. |
Job Polling (gateway devices)
Used by a gateway device to ask "do you have a message for me to send?" This is how the Android app fetches queued jobs; build against it directly only if you're writing your own gateway client.
| Field | Type | Description | |
|---|---|---|---|
| device_id | string | required | The polling device's ID (accepts device as an alias). |
| api_key | string | optional | Only needed the very first time a brand-new device polls, to auto-register it. |
Only the account's default device is ever handed a job — this prevents two phones on the same account from racing for the same message.
{
"status": "found",
"id": "4821",
"phone": "260977000000",
"message": "Your order #4821 has shipped.",
"sim_slot": 0
}
{ "status": "none" }
Delivery Status Report
Called by a gateway device after it attempts to send, to report the outcome back to the platform.
| Field | Type | Description | |
|---|---|---|---|
| id | int | required | The message id returned by /api or fetched via job polling. |
| status | string | required | SENT, FAILED, or DELIVERED. Anything else is recorded as FAILED. |
| device_id | string | optional | Reporting device, for the audit trail. |
Receive Inbound SMS
Called by the gateway app the moment it receives an SMS on the phone, forwarding it into your inbox in real time (single message — see the next section for bulk sync).
| Field | Type | Description | |
|---|---|---|---|
| sender | string | required | Number that sent the SMS. |
| message | string | required | SMS body. |
| device_id | string | optional | Receiving device. |
| sim_slot | int | optional | Which SIM received it. |
Bulk Message Sync
Bulk-syncs a batch of native SMS threads (inbox and/or sent) from the phone into the platform's Messages view. /sync_inbox.php and /sync_messages.php are aliases — both run the exact same handler.
| Field | Type | Description | |
|---|---|---|---|
| device_id | string | required | Syncing device (auto-registered if unseen before). |
| phone_name | string | optional | Friendly device name if not already set. |
| messages | array | required | Array of message objects (form field messages as a JSON string, or a top-level JSON array field — both accepted). |
Message Object
| Field | Type | Description |
|---|---|---|
| id | string | Native message ID on the device (used to de-duplicate re-syncs). |
| address | string | Other party's number. |
| body | string | Message text. |
| date | int | Epoch milliseconds. |
| read | int | 1 if read, 0 otherwise. |
| box | string | inbox or sent. |
{ "success": true, "inserted": 14 }
Dashboard
Your account's home base at /dashboard — everything below is a real, working feature, not a placeholder.
Wallet & Mobile Money Top-Up
Buy more SMS credits instantly from the dashboard using MTN, Airtel, or Zamtel Mobile Money — processed via the Lenco payments network.
| Plan | Amount | Credits |
|---|---|---|
| Starter | K50 | 625 |
| Basic | K100 | 1,250 |
| Growth | K200 | 2,500 |
| Pro | K300 | 3,750 |
| Business | K500 | 6,250 |
| Enterprise | K1,000 | 12,500 |
Every plan works out to the same rate: 1 credit ≈ K0.08. Custom amounts are accepted too, with a K50 minimum per top-up.
Devices Page
Manage every gateway phone connected to your account at /devices.
Messages & Inbox
A two-way messaging view at /messages — not just an outbox.
Send SMS (Web Form)
A manual, one-off sending form at /send-sms for testing or ad-hoc messages without touching the API — useful for verifying a device is wired up correctly.
Lets you pick a specific active device and SIM slot per send, and debits the same 1-credit-per-message balance as the API.
AI Assistant
A built-in chat assistant, available to logged-in accounts.
| Field | Type | Description | |
|---|---|---|---|
| message | string | required | Your message to the assistant. |
Supported Networks
All three major Zambian mobile networks are supported for both sending and mobile money top-up.
Phone Number Formatting
Recipient numbers are normalized automatically to the 260XXXXXXXXX international format — non-digit characters are stripped first.
| You send | Normalized to | Rule |
|---|---|---|
| 977000000 | 260977000000 | 9 digits → prefix with 260 |
| 0977000000 | 260977000000 | Leading 0 + 10 digits → drop the 0, prefix with 260 |
| 260977000000 | 260977000000 | Already in international format → unchanged |
Rate Limits
Rate limits currently apply to authentication endpoints only, tracked per client IP address. The /api send endpoint and device endpoints are not IP-rate-limited — your SMS balance is the natural throttle.
Error Reference
Actual error payloads returned by the API and device endpoints — every response is JSON with an error field describing what went wrong.
| HTTP | error | Cause |
|---|---|---|
| 405 | POST required | Wrong HTTP method used. |
| 401 | Invalid API key | Key missing, mistyped, or revoked (e.g. after a regeneration). |
| 402 | Insufficient SMS balance | Balance is 0 and the account isn't unlimited. |
| 400 | to and message are required | Missing recipient or body on /api. |
| 400 | message too long | Body exceeds 1,600 characters. |
| 400 | device_id required | Missing on /register_device.php. |
| 400 | device_id and fcm_token required | Missing on /register_fcm_token.php. |
| 400 | sender and message required | Missing on /receive_sms.php. |
| 400 | invalid messages JSON | Malformed payload on /sync_inbox.php / /sync_messages.php. |
| 500 | Failed to queue message | Database error while inserting the job — rare, safe to retry. |
Security Model
cURL
curl -X POST https://sms.wilcybertek.com/api \
-H "X-API-Key: wp_live_xxxx-xxxx-..." \
-H "Content-Type: application/json" \
-d '{"to":"260977000000","message":"Hello from cURL"}'
JavaScript (fetch)
const res = await fetch("https://sms.wilcybertek.com/api", {
method: "POST",
headers: {
"X-API-Key": "wp_live_xxxx-xxxx-...",
"Content-Type": "application/json"
},
body: JSON.stringify({
to: "260977000000",
message: "Hello from JavaScript"
})
});
const data = await res.json();
console.log(data);
Python
import requests
resp = requests.post(
"https://sms.wilcybertek.com/api",
headers={"X-API-Key": "wp_live_xxxx-xxxx-..."},
json={"to": "260977000000", "message": "Hello from Python"},
)
print(resp.json())
PHP
<?php
$ch = curl_init("https://sms.wilcybertek.com/api");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-API-Key: wp_live_xxxx-xxxx-...",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"to" => "260977000000",
"message" => "Hello from PHP",
]),
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;