Send and receive SMS through your own paired Android phones with a simple REST API. Base URL: https://sms.zoril.app
pk_live_.curl -X POST https://sms.zoril.app/api/public/v1/sms/send \
-H "Authorization: Bearer pk_live_..." \
-H "Content-Type: application/json" \
-d '{"to":"+15551234567","message":"Hello from Zoril SMS"}'Every request must include a bearer token in the Authorization header:
Authorization: Bearer pk_live_your_key_hereRevoked keys are permanently deleted and stop working immediately. Store keys in environment variables — never in client-side code or public repos.
POST/api/public/v1/sms/send
| Field | Type | Description |
|---|---|---|
| to | string | Recipient phone number in E.164 format (e.g. +15551234567). |
| message | string | SMS body, 1–1600 characters. Long messages are auto-segmented on the device. |
| device_id | uuid? | Optional. If omitted, we route to the most recently online paired device. |
202 Accepted{
"id": "b1e5c8f3-...-2a",
"status": "queued",
"to": "+15551234567",
"created_at": "2026-07-09T13:14:15.000Z"
}Simulator devices return status: "delivered" immediately. Real devices start at queued and progress to sent → delivered (or failed).
const res = await fetch("https://sms.zoril.app/api/public/v1/sms/send", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.ZORIL_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({ to: "+15551234567", message: "Hi from Node" }),
});
const data = await res.json();
console.log(data.id, data.status);import os, requests
r = requests.post(
"https://sms.zoril.app/api/public/v1/sms/send",
headers={"Authorization": f"Bearer {os.environ['ZORIL_API_KEY']}"},
json={"to": "+15551234567", "message": "Hi from Python"},
timeout=15,
)
r.raise_for_status()
print(r.json())GET/api/public/v1/messages/{id}
curl https://sms.zoril.app/api/public/v1/messages/b1e5c8f3-...-2a \
-H "Authorization: Bearer pk_live_..."{
"id": "b1e5c8f3-...-2a",
"direction": "out",
"to_number": "+15551234567",
"from_number": null,
"body": "Hello from Zoril SMS",
"status": "delivered",
"error": null,
"created_at": "2026-07-09T13:14:15.000Z",
"sent_at": "2026-07-09T13:14:17.000Z",
"delivered_at":"2026-07-09T13:14:19.000Z",
"retry_count": 0
}Status values: queued, sent, delivered, failed, received (inbound).
Paired Android devices post incoming SMS to Zoril automatically — you do not need to call this endpoint yourself. It is documented for custom integrations.
POST/api/public/v1/sms/inbound
{
"from": "+15551234567",
"message": "STOP",
"device_id": "optional-uuid",
"received_at": "2026-07-09T13:14:20.000Z"
}Inbound messages trigger your enabled automations (keyword, regex, or catch-all → auto-reply, forward, or webhook) and fire the message.inbound webhook event.
Add a webhook URL under Webhooks to receive signed events for inbound and outbound message updates.
POST https://your-app.example.com/hook
content-type: application/json
x-zoril-signature: sha256=<hex>
x-zoril-event: message.inbound
x-zoril-timestamp: 1783600455
{
"event": "message.inbound",
"timestamp": "2026-07-09T13:14:20.000Z",
"data": {
"id": "...",
"from_number": "+15551234567",
"body": "STOP",
"device_id": "...",
"created_at": "..."
}
}import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, header, secret) {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(header ?? "");
const b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}We retry failed deliveries with exponential backoff. Respond with 2xx within 10 seconds to acknowledge.
Pass a unique Idempotency-Key header on POST /sms/send to safely retry on network failures. Duplicate requests with the same key return the original message with idempotent: true.
Idempotency-Key: 8f14e45f-ceea-467a-a7a1-d59d1cbb0f14402 quota_exceeded.Errors use standard HTTP status codes and a JSON body: { "error": "code", "details": "..." }
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_input | Body failed validation. |
| 401 | missing_api_key / invalid_api_key | Bearer token missing, wrong, or revoked. |
| 402 | quota_exceeded | Monthly SMS quota reached. |
| 404 | device_not_found / not_found | Resource does not exist on your account. |
| 409 | no_online_device | No paired device is online to send from. |
| 429 | rate_limited | Too many requests — back off and retry. |
| 5xx | server_error | Retry with idempotency key. |
Pair any Android phone (Android 8+) by installing the Zoril SMS gateway APK and scanning the pairing QR code shown in Devices → Add device. The app runs a foreground service that polls Zoril for outbound messages, sends them via the phone's SIM, acknowledges delivery, and forwards inbound SMS back to your account.