Integrate SMS Localhost into your application with our REST API.
Using WhatsApp? There's a dedicated guide. Connecting your number, agents, templates, campaigns and the WhatsApp REST APIAll API requests require an API key passed via the X-API-KEY header.
curl -H "X-API-KEY: your_api_key_here" \
https://sms.localhost.co.zw/api/v1/sms/history/import requests
headers = {"X-API-KEY": "your_api_key_here"}
response = requests.get(
"https://sms.localhost.co.zw/api/v1/sms/history/",
headers=headers
)
print(response.json())const response = await fetch(
"https://sms.localhost.co.zw/api/v1/sms/history/",
{ headers: { "X-API-KEY": "your_api_key_here" } }
);
const data = await response.json();
console.log(data);$ch = curl_init("https://sms.localhost.co.zw/api/v1/sms/history/");
curl_setopt($ch, CURLOPT_HTTPHEADER, [
"X-API-KEY: your_api_key_here"
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
$data = json_decode($response, true);HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://sms.localhost.co.zw/api/v1/sms/history/"))
.header("X-API-KEY", "your_api_key_here")
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString()
);
System.out.println(response.body());Generate API keys from your API Keys page in the dashboard.
/api/v1/auth/keys/
List active API keys or create a new one.
/api/v1/auth/keys/{key_id}/
Revoke an API key.
/api/v1/sms/send/
Send an SMS message.
curl -X POST https://sms.localhost.co.zw/api/v1/sms/send/ \
-H "X-API-KEY: your_api_key" \
-H "Content-Type: application/json" \
-d '{"to": "0771234567", "sender": "MyBrand", "message": "Hello!"}'import requests
response = requests.post(
"https://sms.localhost.co.zw/api/v1/sms/send/",
headers={
"X-API-KEY": "your_api_key",
"Content-Type": "application/json",
},
json={
"to": "0771234567",
"sender": "MyBrand",
"message": "Hello!"
}
)
print(response.json())
# {"message_id": "...", "channel": "sms", "status": "sent", ...}const response = await fetch(
"https://sms.localhost.co.zw/api/v1/sms/send/",
{
method: "POST",
headers: {
"X-API-KEY": "your_api_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
to: "0771234567",
sender: "MyBrand",
message: "Hello!",
}),
}
);
const data = await response.json();
console.log(data);
// {message_id: "...", channel: "sms", status: "sent", ...}$ch = curl_init("https://sms.localhost.co.zw/api/v1/sms/send/");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-API-KEY: your_api_key",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"to" => "0771234567",
"sender" => "MyBrand",
"message" => "Hello!",
]),
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
$data = json_decode($response, true);
print_r($data);String json = """
{"to":"0771234567","sender":"MyBrand","message":"Hello!"}
""";
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://sms.localhost.co.zw/api/v1/sms/send/"))
.header("X-API-KEY", "your_api_key")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString()
);
System.out.println(response.body());{
"message_id": "a1b2c3d4-...",
"channel": "sms",
"status": "sent",
"to": "0771234567",
"sender": "MyBrand",
"sms_credits": 142
}
/api/v1/sms/history/
Retrieve the last 100 sent messages.
curl https://sms.localhost.co.zw/api/v1/sms/history/ \
-H "X-API-KEY: your_api_key"import requests
response = requests.get(
"https://sms.localhost.co.zw/api/v1/sms/history/",
headers={"X-API-KEY": "your_api_key"}
)
messages = response.json()["messages"]
for msg in messages:
print(f"{msg['recipient']}: {msg['status']}")const response = await fetch(
"https://sms.localhost.co.zw/api/v1/sms/history/",
{ headers: { "X-API-KEY": "your_api_key" } }
);
const { messages } = await response.json();
messages.forEach(msg =>
console.log(`${msg.recipient}: ${msg.status}`)
);$ch = curl_init("https://sms.localhost.co.zw/api/v1/sms/history/");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-KEY: your_api_key"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data = json_decode(curl_exec($ch), true);
foreach ($data["messages"] as $msg) {
echo $msg["recipient"] . ": " . $msg["status"] . "\n";
}/api/v1/sms/senders/
List your Sender IDs or request a new one.
/api/v1/sms/testers/
List your verified tester numbers.
/api/v1/contacts/
List contacts or create a new one.
# Create a contact
curl -X POST https://sms.localhost.co.zw/api/v1/contacts/ \
-H "X-API-KEY: your_api_key" \
-H "Content-Type: application/json" \
-d '{"phone": "0771234567", "first_name": "John", "last_name": "Doe"}'import requests
# Create a contact
response = requests.post(
"https://sms.localhost.co.zw/api/v1/contacts/",
headers={
"X-API-KEY": "your_api_key",
"Content-Type": "application/json",
},
json={
"phone": "0771234567",
"first_name": "John",
"last_name": "Doe",
}
)
print(response.json())// Create a contact
const response = await fetch(
"https://sms.localhost.co.zw/api/v1/contacts/",
{
method: "POST",
headers: {
"X-API-KEY": "your_api_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
phone: "0771234567",
first_name: "John",
last_name: "Doe",
}),
}
);
console.log(await response.json());// Create a contact
$ch = curl_init("https://sms.localhost.co.zw/api/v1/contacts/");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"X-API-KEY: your_api_key",
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"phone" => "0771234567",
"first_name" => "John",
"last_name" => "Doe",
]),
CURLOPT_RETURNTRANSFER => true,
]);
$data = json_decode(curl_exec($ch), true);/api/v1/contacts/{contact_id}/
Retrieve, update, or delete a single contact.
/api/v1/contacts/groups/
List contact groups or create a new one.
/api/v1/campaigns/
List campaigns or create a new one.
curl -X POST https://sms.localhost.co.zw/api/v1/campaigns/ \
-H "X-API-KEY: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "March Promo",
"message": "Hi {name}, enjoy 20% off this week!",
"sender_name": "MyBrand",
"group_id": "your_group_uuid"
}'import requests
response = requests.post(
"https://sms.localhost.co.zw/api/v1/campaigns/",
headers={
"X-API-KEY": "your_api_key",
"Content-Type": "application/json",
},
json={
"name": "March Promo",
"message": "Hi {name}, enjoy 20% off this week!",
"sender_name": "MyBrand",
"group_id": "your_group_uuid",
}
)
campaign = response.json()
print(f"Campaign created: {campaign['id']}")const response = await fetch(
"https://sms.localhost.co.zw/api/v1/campaigns/",
{
method: "POST",
headers: {
"X-API-KEY": "your_api_key",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "March Promo",
message: "Hi {name}, enjoy 20% off this week!",
sender_name: "MyBrand",
group_id: "your_group_uuid",
}),
}
);
const campaign = await response.json();
console.log(`Campaign created: ${campaign.id}`);/api/v1/campaigns/{campaign_id}/send/
Launch a campaign for sending.
/api/v1/campaigns/{campaign_id}/pause/
Pause a running campaign.
/api/v1/campaigns/{campaign_id}/cancel/
Cancel a campaign.
/api/v1/billing/balance/
Get your current SMS credit balance.
curl https://sms.localhost.co.zw/api/v1/billing/balance/ \
-H "X-API-KEY: your_api_key"import requests
response = requests.get(
"https://sms.localhost.co.zw/api/v1/billing/balance/",
headers={"X-API-KEY": "your_api_key"}
)
data = response.json()
print(f"Credits remaining: {data['sms_credits']}")const response = await fetch(
"https://sms.localhost.co.zw/api/v1/billing/balance/",
{ headers: { "X-API-KEY": "your_api_key" } }
);
const data = await response.json();
console.log(`Credits remaining: ${data.sms_credits}`);$ch = curl_init("https://sms.localhost.co.zw/api/v1/billing/balance/");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-KEY: your_api_key"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data = json_decode(curl_exec($ch), true);
echo "Credits remaining: " . $data["sms_credits"];/api/v1/billing/transactions/
View your transaction history.
LinksAPI automatically shortens links in your outgoing messages using branded
rerout.co short links. Shorter links use fewer 160-character SMS segments — so you
spend fewer credits — and every link gets click analytics. It applies to SMS sent from the
dashboard, the Send API, and campaigns, plus free-text WhatsApp messages.
There is nothing to call — it runs server-side at send time. Turn it on per organization at
LinksAPI in your dashboard; from then on any
https:// link in your message is replaced automatically before it is sent and billed.
In per-recipient mode you can enable one-time links: each link stops redirecting after its first click — ideal for single-use survey or verification links. The first click is served, then the link is deactivated automatically.
Total clicks plus top countries and referrers are shown for every link on your LinksAPI page. Click data is aggregated and never exposes recipients' raw IP addresses.
Check the live delivery status of Zimbabwean mobile network operators (NetOne, Econet, Telecel) so your application can react to carrier-side incidents — e.g. pause sends, switch fallback routes, or surface a banner to your own users. You can also subscribe to webhook events that fire the moment a status changes (see Webhooks).
/api/v1/status/
Returns the current status for every MNO. Possible state values: operational, degraded, outage, maintenance.
curl https://sms.localhost.co.zw/api/v1/status/ \
-H "X-API-KEY: your_api_key"import requests
statuses = requests.get(
"https://sms.localhost.co.zw/api/v1/status/",
headers={"X-API-KEY": "your_api_key"},
).json()
for s in statuses:
if s["state"] != "operational":
print(f"{s['mno_display']} is {s['state']}: {s['note']}")const res = await fetch(
"https://sms.localhost.co.zw/api/v1/status/",
{ headers: { "X-API-KEY": "your_api_key" } }
);
const statuses = await res.json();
statuses
.filter(s => s.state !== "operational")
.forEach(s => console.warn(`${s.mno_display}: ${s.state}`));Sample response
[
{"mno":"netone", "mno_display":"NetOne", "state":"outage", "state_display":"Outage", "note":"NetOne core network issue, ETA 2h", "updated_at":"2026-05-12T10:00:00Z"},
{"mno":"econet", "mno_display":"Econet", "state":"operational", "state_display":"Operational", "note":"", "updated_at":"2026-05-10T08:32:11Z"},
{"mno":"telecel", "mno_display":"Telecel", "state":"operational", "state_display":"Operational", "note":"", "updated_at":"2026-05-10T08:32:11Z"}
]
/api/v1/status/incidents/
History of state transitions. Optional query params: mno=netone|econet|telecel, limit=N (default 50, max 200). Incidents with ended_at: null are still in progress.
curl "https://sms.localhost.co.zw/api/v1/status/incidents/?mno=netone&limit=10" \
-H "X-API-KEY: your_api_key"Webhooks let us POST events to your server the instant they happen — no polling required. Register an endpoint at Settings → Webhooks, tick the events you care about, and copy the signing secret we generate (shown only once).
sms.delivered — an SMS reached its recipientsms.failed — an SMS failed deliverymno.status.changed — an MNO's delivery state changed (outage, degraded, recovered, maintenance)X-Webhook-Signature header (HMAC-SHA256 of the raw body, hex-encoded, using your secret).POST https://your-server.com/webhook
Content-Type: application/json
X-Webhook-Signature: 4f9c1a...e7
{
"event": "mno.status.changed",
"mno": "netone",
"previous_state": "operational",
"state": "outage",
"note": "NetOne core network issue, ETA 2h",
"incident_id": "5b6f2e4c-...",
"timestamp": "2026-05-12T10:00:00Z"
}
Always verify X-Webhook-Signature against the raw request body — not a re-serialized JSON — using your webhook's secret:
import hmac, hashlib
from flask import Flask, request, abort
SECRET = b"your_webhook_secret"
app = Flask(__name__)
@app.post("/webhook")
def webhook():
expected = hmac.new(SECRET, request.get_data(), hashlib.sha256).hexdigest()
got = request.headers.get("X-Webhook-Signature", "")
if not hmac.compare_digest(expected, got):
abort(401)
event = request.get_json()
if event["event"] == "mno.status.changed" and event["state"] in ("outage", "degraded"):
pause_sends_for(event["mno"])
return "", 204const express = require("express");
const crypto = require("crypto");
const app = express();
const SECRET = "your_webhook_secret";
app.post("/webhook", express.raw({ type: "application/json" }), (req, res) => {
const expected = crypto.createHmac("sha256", SECRET).update(req.body).digest("hex");
const got = req.headers["x-webhook-signature"] || "";
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(got))) return res.sendStatus(401);
const event = JSON.parse(req.body.toString());
if (event.event === "mno.status.changed" && ["outage", "degraded"].includes(event.state)) {
pauseSendsFor(event.mno);
}
res.sendStatus(204);
});$secret = "your_webhook_secret";
$body = file_get_contents("php://input");
$expected = hash_hmac("sha256", $body, $secret);
$got = $_SERVER["HTTP_X_WEBHOOK_SIGNATURE"] ?? "";
if (!hash_equals($expected, $got)) { http_response_code(401); exit; }
$event = json_decode($body, true);
if ($event["event"] === "mno.status.changed" && in_array($event["state"], ["outage", "degraded"])) {
pause_sends_for($event["mno"]);
}
http_response_code(204);All errors return a JSON object with an error key.
{"error": "Insufficient SMS credits."}