SMS API Documentation
Integrate SMS capabilities into your applications, billing system, or website with our RESTful API. Send single or bulk messages, check delivery status, and manage your account programmatically.
Introduction
The 4K Net SMS API lets you send SMS messages programmatically from your own application, billing system, or captive/hotspot portal. It's RESTful, uses JSON for request and response bodies on the main endpoints, and supports both single and bulk messaging. A legacy-compatible Billing Gateway route is also available for older systems (PHPNuxBill, WHMCS, Mikrotik hotspot billing) that can only call a fixed URL.
Bearer Authorization header (recommended) or as an api_key value in the request body/query string.
Authentication
Every endpoint below (except Test Connection) requires your API key. There are two accepted forms — use whichever your integration supports:
Header (JSON endpoints)
Authorization: Bearer YOUR_API_KEY
Query/body param (billing systems)
?api_key=YOUR_API_KEY
Your API key is generated automatically when you create an account, and can be rotated or revoked any time on your Developer page.
Base URL
All API requests should be made to:
https://sms.4knet.co.ke/api/v1/
For example, to check your balance you'd call https://sms.4knet.co.ke/api/v1/balance.
Machine-readable OpenAPI JSON is available at https://sms.4knet.co.ke/openapi.json.
All Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/test | Test API connectivity (no auth required) |
| POST / GET | /api/v1/sms/send | Send a single SMS message |
| POST | /api/v1/sms/send-bulk | Send SMS to multiple recipients (max 100/request) |
| POST / GET | /api/v1/sms/status | Check the delivery status of a sent message |
| GET | /api/v1/balance | Get your account balance and default sender ID |
| GET | /api/v1/sender-ids | List your Sender ID applications and approvals |
| POST | /api/v1/sender-ids/apply | Apply for a Sender ID |
| POST | /api/v1/sender-ids/default | Set an approved Sender ID as your default |
| GET | /api/v1/wallet/balance | Get available, reserved, and effective wallet balance |
| GET | /api/v1/wallet/ledger | Get paginated wallet ledger entries |
| POST / GET | /api/v1/billing-gateway | Legacy billing-system integration (PHPNuxBill/WHMCS compatible) |
Rate limit: 60 requests/minute per API key across all endpoints above.
Send SMS
Send a single SMS message to one recipient.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| recipient | String | Yes | Kenyan phone number (2547XXXXXXXX, 2541XXXXXXXX, or 07XXXXXXXX — auto-normalized) |
| message | String | Yes | SMS content, up to 1600 characters (160 = 1 unit, then ~153/unit) |
| sender_id | String | Optional | Approved Sender ID. If omitted, your default approved Sender ID is used. |
| scheduled_at | Date-time | Optional | Future ISO timestamp. Wallet balance is reserved until processing. |
For POST requests, include an optional Idempotency-Key header to safely retry without double-charging. See Idempotency.
Response Example (Success)
{
"success": true,
"message": "SMS message successfully dispatched downstream.",
"recipient": "254712345678",
"units": 1,
"cost": 0.29,
"new_balance": 92.71,
"bytewave_uid": "8f2c1a9e0b3d4c5f"
}
curl -X POST https://sms.4knet.co.ke/api/v1/sms/send \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"recipient":"254712345678","message":"Hello from cURL"}'
const res = await fetch("https://sms.4knet.co.ke/api/v1/sms/send", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
recipient: "254712345678",
message: "Hello from Node.js"
})
});
const data = await res.json();
console.log(data);
$ch = curl_init("https://sms.4knet.co.ke/api/v1/sms/send");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer YOUR_API_KEY",
"Content-Type: application/json"
],
CURLOPT_POSTFIELDS => json_encode([
"recipient" => "254712345678",
"message" => "Hello from PHP"
])
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
print_r($response);
import requests
response = requests.post(
"https://sms.4knet.co.ke/api/v1/sms/send",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"recipient": "254712345678", "message": "Hello from Python"}
)
print(response.json())
GET Variant (billing/hotspot systems)
For systems that can only call a fixed URL with placeholder substitution, no custom headers:
GET https://sms.4knet.co.ke/api/v1/sms/send?api_key=YOUR_API_KEY¶m_number=254712345678¶m_text=Hello
Bulk SMS
Send the same message to multiple recipients in one call (max 100 per request). Each recipient is charged and logged individually, exactly as if you'd called Send SMS once per number.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| recipients | Array | Yes | Array of phone numbers, max 100 entries |
| message | String | Yes | SMS content sent to every recipient |
Request Example
{
"recipients": ["254712345678", "254700000001"],
"message": "Special offer just for you!"
}
Response Example
{
"success": true,
"message": "Bulk SMS sent: 2 successful, 0 failed",
"statistics": {
"total_attempted": 2,
"successful": 2,
"failed": 0,
"invalid_numbers": 0
},
"units_used": 2,
"total_cost": 0.58,
"new_balance": 92.13,
"results": [
{ "recipient": "254712345678", "message_id": "8f2c1a9e0b3d4c5f", "success": true },
{ "recipient": "254700000001", "message_id": "a41e9b2c7d6f0a1b", "success": true }
],
"invalid_numbers": [],
"failed_recipients": {}
}
curl -X POST https://sms.4knet.co.ke/api/v1/sms/send-bulk \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"recipients":["254712345678","254700000001"],"message":"Special offer just for you!"}'
Check Status
Look up the delivery status of a message you previously sent through the API.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| message_id | String | Yes | The bytewave_uid returned from Send SMS or Bulk SMS |
Response Example
{
"success": true,
"message_id": "8f2c1a9e0b3d4c5f",
"recipient": "254712345678",
"sender_id": "BytewaveSMS",
"status": "Delivered",
"cost": 0.29,
"created_at": "2026-07-25 11:21:56",
"delivered_at": "2026-07-25 11:22:03",
"message_preview": "Hello from cURL"
}
curl "https://sms.4knet.co.ke/api/v1/sms/status?api_key=YOUR_API_KEY&message_id=8f2c1a9e0b3d4c5f"
Status is one of Pending, Sent, Delivered, Failed, or Expired. delivered_at is null until a delivery receipt has arrived.
Get Balance
Get your current account balance, default sender ID, and per-unit rate.
Response Example
{
"success": true,
"user_id": 12,
"name": "Jane Wanjiru",
"email": "jane@example.com",
"sms_balance": 92.13,
"default_sender_id": "BytewaveSMS",
"sms_cost_per_unit": 0.29,
"timestamp": "2026-07-25T11:21:55.000Z"
}
curl -H "Authorization: Bearer YOUR_API_KEY" https://sms.4knet.co.ke/api/v1/balance
const res = await fetch("https://sms.4knet.co.ke/api/v1/balance", {
headers: { "Authorization": "Bearer YOUR_API_KEY" }
});
console.log(await res.json());
$response = Invoke-RestMethod -Uri "https://sms.4knet.co.ke/api/v1/balance" `
-Headers @{ Authorization = "Bearer YOUR_API_KEY" }
$response | ConvertTo-Json
Sender IDs
Manage the alphanumeric Sender IDs your API key is allowed to use. A custom Sender ID must be approved before it can be used for sending.
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /api/v1/sender-ids | List your Sender IDs with approval/default status |
| POST | /api/v1/sender-ids/apply | Submit a new Sender ID application |
| POST | /api/v1/sender-ids/default | Set an approved Sender ID as default |
curl -H "Authorization: Bearer YOUR_API_KEY" https://sms.4knet.co.ke/api/v1/sender-ids
curl -X POST https://sms.4knet.co.ke/api/v1/sender-ids/apply \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: sender-id-application-001" \
-d '{"sender_id":"MYBRAND"}'
{
"success": true,
"message": "Sender ID application submitted.",
"data": {
"sender_id": "MYBRAND",
"status": "Pending Review",
"is_default": 0
}
}
Wallet
Use the wallet endpoints for read-only balance and ledger checks. Top-ups and payment gateway settings are handled in the web dashboard, not through public API calls.
Balance
GET /api/v1/wallet/balance
Ledger
GET /api/v1/wallet/ledger?limit=50&offset=0
{
"success": true,
"message": "Wallet balance retrieved.",
"data": {
"available_balance": 92.13,
"reserved_balance": 0,
"effective_balance": 92.13
}
}
curl -H "Authorization: Bearer YOUR_API_KEY" https://sms.4knet.co.ke/api/v1/wallet/ledger?limit=25&offset=0
Test Connection
Test API connectivity and service status.
{
"success": true,
"status": "online",
"service": "4K Net Bulk SMS API",
"version": "1.0.0",
"database": "connected",
"default_sender_id": "BytewaveSMS",
"timestamp": "2026-07-25T11:21:55.000Z"
}
curl https://sms.4knet.co.ke/api/v1/test
Billing Gateway
A drop-in compatible endpoint for legacy billing/hotspot systems (PHPNuxBill, WHMCS SMS modules, Mikrotik) that call a fixed URL with their own parameter names. Accepts both GET and POST, with parameters in the URL or the request body.
Supported Parameter Aliases
| Parameter | Aliases | Required | Description |
|---|---|---|---|
| api_key | secret | Yes | Your API key |
| recipient | number, param_number, to | Yes | Recipient phone number |
| message | text, param_text | Yes | SMS message content |
| format | — | Optional | Response format: simple (default) or json |
Request Examples
GET /api/v1/billing-gateway?api_key=YOUR_API_KEY¶m_number=254712345678¶m_text=Your+bill+is+ready
POST /api/v1/billing-gateway Content-Type: application/x-www-form-urlencoded api_key=YOUR_API_KEY&number=254712345678&text=Your+bill+is+ready
Response Examples
Simple format (default) — OK|message_id|recipient|units_used|new_balance
OK|8f2c1a9e0b3d4c5f|254712345678|1|92.13
JSON format (?format=json)
{
"status": "success",
"message": "SMS message successfully dispatched downstream.",
"data": {
"message_id": "8f2c1a9e0b3d4c5f",
"recipient": "254712345678",
"units_used": 1,
"new_balance": 92.13
}
}
Error response (either format)
ERROR|Recipient must be a valid Kenyan phone number.
Idempotency
For retry-safe chargeable actions, send a unique Idempotency-Key header. It is currently supported on POST /api/v1/sms/send and POST /api/v1/sender-ids/apply.
| Retry Scenario | Result |
|---|---|
| Same key and same request body | Original response is returned with X-Cache-Lookup: HIT-IDEMPOTENT |
| Same key with a different body | HTTP 409 with IDEMPOTENCY_KEY_REUSED |
| Same key while first request is still processing | HTTP 409 with IDEMPOTENCY_REQUEST_IN_PROGRESS |
curl -X POST https://sms.4knet.co.ke/api/v1/sms/send \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-100045-sms-1" \
-d '{"recipient":"254712345678","message":"Payment received"}'
Webhook Payload & Signature Verification
Configure a webhook URL on your Developer page to get notified the instant a message you sent through the API changes status — no polling Check Status required.
// POST to your configured webhook URL, whenever a message sent via the API changes status
{
"event": "sms.status_updated",
"message_id": "8f2c1a9e0b3d4c5f",
"phone": "254712345678",
"status": "Delivered",
"timestamp": "2026-07-25T11:22:03.000Z"
}
// Node.js — verifying X-Webhook-Signature
const crypto = require("crypto");
function isValidSignature(rawBody, signatureHeader, secret) {
const expected = "sha256=" + crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}
Error Codes
| HTTP Code | Error Type | Description |
|---|---|---|
| 400 | Bad Request | Invalid parameters, bad phone number, message too long, or insufficient balance |
| 401 | Unauthorized | Missing, invalid, or revoked API key |
| 403 | Forbidden | Account is suspended |
| 404 | Not Found | Linked account or message_id not found |
| 405 | Method Not Allowed | Wrong HTTP method used for this endpoint |
| 429 | Too Many Requests | Rate limit exceeded (60 requests/minute per API key) |
| 500 | Internal Server Error | Unexpected server-side error |
| 503 | Service Unavailable | Upstream SMS carrier is temporarily unreachable — retry shortly |
Testing Tools
Every example above is copy-pasteable. To confirm your setup end-to-end without any risk of a real charge, start with Test Connection (no key needed) then Get Balance (read-only) before attempting a real Send.
# 1. Confirm the API is reachable
curl https://sms.4knet.co.ke/api/v1/test
# 2. Confirm your key works and check your balance
curl -H "Authorization: Bearer YOUR_API_KEY" https://sms.4knet.co.ke/api/v1/balance
# 3. Send a real test message to your own number
curl -X POST https://sms.4knet.co.ke/api/v1/sms/send \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"recipient":"2547XXXXXXXX","message":"Test message"}'
Frequently Asked Questions
What phone number formats are accepted?
We accept 2547XXXXXXXX, 2541XXXXXXXX, or a local 07XXXXXXXX / 01XXXXXXXX number — the API normalizes it to the 254 format automatically before sending.
How are messages charged?
By units: up to 160 characters is 1 unit; longer messages are billed at roughly 153 characters per additional unit (standard concatenated-SMS segmenting). Each unit costs your account's per-unit rate, shown in the Get Balance response.
What sender ID will my messages use?
Your account's default sender ID (also returned as default_sender_id from Get Balance). It's managed from your Profile page.
Is there a rate limit?
Yes — 60 requests per minute per API key across all /api/v1/* endpoints. Exceeding it returns HTTP 429.
How can I test without spending balance?
Test Connection needs no API key and never touches your account. Get Balance and Check Status are read-only. Only Send SMS, Bulk SMS, and the Billing Gateway route ever dispatch a real message and deduct balance.
What's the difference between the main API and the Billing Gateway?
Same underlying send logic — the Billing Gateway just also accepts the alternate parameter names (number, secret, etc.) and pipe-delimited plain-text responses that older billing/hotspot systems expect, instead of requiring a JSON body and Bearer header.
Need Help?
If you run into trouble integrating:
- Check the
errormessage in the API response — they're written to be specific and actionable - Verify your API key is correct and hasn't been revoked on your Developer page
- Confirm your account isn't suspended and has sufficient balance
- Double-check the phone number format against the FAQ above