You send a single HTTPS request. The relay queues it. Your phone delivers it via the selected channel.
Authorization: Bearer CLIENT_API_KEYapplication/jsonCLIENT_API_KEY private. If it leaks, rotate/revoke it in the portal and reconfigure clients.
POST/api/v1/messages{
"phone_id": "phone_main",
"channel": "sms",
"to": "+15551234567",
"text": "hello",
"source": "my-app",
"idempotency_key": "my-unique-id-001"
}
| Field | Required | Notes |
|---|---|---|
phone_id |
Yes | The phone device identifier you configured. |
channel |
Yes | One of sms, wa, tg, notify, email,
tg_bot, pchat. |
to |
Depends | Required for sms/wa/tg/email. For tg,
this is typically a username like @name. |
text |
Yes | Message body. |
source |
No | Any string for your logs/visibility. |
idempotency_key |
No | If you retry a request with the same key, the relay returns the same message_id. |
subject |
No | Email subject (required for email). For local notifications you may use title
or subject. |
bot_token |
No | Required only for tg_bot. |
email and tg_bot may be delivered server-side (and can return
status: sent immediately) depending on relay configuration.
// 202 Accepted (queued)
{ "success": true, "message_id": "msg_...", "status": "queued", "idempotency_key": "..." }
// 200 OK (duplicate idempotency_key)
{ "success": true, "duplicate": true, "message_id": "msg_...", "status": "queued", "idempotency_key": "..." }
// WhatsApp
{ "phone_id":"phone_main","channel":"wa","to":"+15551234567","text":"hello" }
// Telegram (username)
{ "phone_id":"phone_main","channel":"tg","to":"@name","text":"hello" }
// Local notification (no "to" required)
{ "phone_id":"phone_main","channel":"notify","title":"Alert","text":"hello" }
// Email
{ "phone_id":"phone_main","channel":"email","to":"me@example.com","subject":"Hi","text":"hello" }
// Telegram Bot (server-side)
{ "phone_id":"phone_main","channel":"tg_bot","to":"123456789","bot_token":"...","text":"hello" }
GET/api/v1/messages/<message_id>// 200 OK
{
"success": true,
"message": {
"message_id": "msg_...",
"status": "queued | sending | sent | failed",
"last_error": null,
"created_at": "2026-02-17T19:40:00+00:00",
"delivered_at": null
}
}
GET /api/v1/messages?limit=50GET /healthGET/POST /api/v1/webchat/inbound/queryGET/POST/DELETE /api/v1/webchat/callbacks/inboundcurl -sS -X POST "$BASE_URL/api/v1/webchat/inbound/query" \
-H "Authorization: Bearer $CLIENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_id":"phone_main",
"range":"7d",
"channels":["sms","wa","tg"],
"event_types":["call","missed","answered"],
"tags":["vip","support"],
"tag_mode":"any",
"source_contains":"+1555",
"min_duration_sec":10,
"has_response":true,
"sort":"slowest",
"limit":200
}'
{
"success": true,
"summary": {
"inbound_total": 120,
"calls_total": 54,
"messages_total": 66,
"answered_calls": 31,
"missed_calls": 15,
"avg_call_duration_sec": 84,
"median_response_min": 3,
"channels": { "sms": 40, "wa": 58, "tg": 22 }
},
"sent_summary": {
"sent_total": 210,
"channels": { "sms": 70, "wa": 100, "tg": 40 }
},
"sent_by_channel": { "sms": 70, "wa": 100, "tg": 40 },
"sent_by_day": [ { "day": "2026-02-21", "count": 19 } ],
"sent_by_person": [ { "destination":"+1555...", "total": 12, "last_sent": "..." } ],
"sent_by_tag": [ { "tag":"support", "count": 34 } ],
"sent_events": [ { "message_id":"msg_...", "destination":"+1555..." } ],
"email_report": {
"inbound_total": 28,
"sent_total": 41,
"top_inbound_senders": [ { "sender":"ops@vendor.com", "count": 8 } ],
"top_sent_recipients": [ { "recipient":"client@acme.com", "count": 11 } ],
"domains": [ { "domain":"vendor.com", "count": 19 } ],
"by_day": [ { "day":"2026-02-21", "count": 7 } ]
},
"hourly": [ { "hour": 9, "count": 12 } ],
"top_contacts": [ { "source":"+1555...", "total":14 } ],
"events": [ { "message_id":"in_...", "event_type":"missed" } ],
"count": 200
}
curl -sS -X POST "$BASE_URL/api/v1/webchat/callbacks/inbound" \
-H "Authorization: Bearer $CLIENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"callback_url":"https://example.com/inbound-hook",
"channels":["sms","wa"],
"source_filter":"+1555",
"event_types":["call","missed","answered"],
"keyword_filter":"support",
"min_duration_sec":5,
"max_duration_sec":900,
"callback_token":"secret-token",
"active":true
}'
BASE_URL="http://127.0.0.1:5110"
CLIENT_API_KEY="..."
PHONE_ID="phone_main"
curl -sS -X POST "$BASE_URL/api/v1/messages" \
-H "Authorization: Bearer $CLIENT_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"phone_id\":\"$PHONE_ID\",\"channel\":\"sms\",\"to\":\"+15551234567\",\"text\":\"hello\",\"source\":\"curl\",\"idempotency_key\":\"sms-001\"}"
import requests
BASE_URL = "http://127.0.0.1:5110"
CLIENT_API_KEY = "..."
payload = {
"phone_id": "phone_main",
"channel": "sms",
"to": "+15551234567",
"text": "hello",
"source": "python",
"idempotency_key": "sms-001",
}
r = requests.post(
f"{BASE_URL}/api/v1/messages",
headers={"Authorization": f"Bearer {CLIENT_API_KEY}", "Content-Type": "application/json"},
json=payload,
timeout=10,
)
print(r.status_code, r.json())
#include <curl/curl.h>
#include <iostream>
#include <string>
int main() {
std::string base = "http://127.0.0.1:5110";
std::string apiKey = "...";
std::string body =
R"({"phone_id":"phone_main","channel":"sms","to":"+15551234567","text":"hello","source":"cpp","idempotency_key":"sms-001"})";
CURL* curl = curl_easy_init();
if (!curl) return 2;
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, ("Authorization: Bearer " + apiKey).c_str());
headers = curl_slist_append(headers, "Content-Type: application/json");
curl_easy_setopt(curl, CURLOPT_URL, (base + "/api/v1/messages").c_str());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str());
CURLcode res = curl_easy_perform(curl);
std::cerr << "curl=" << res << std::endl;
curl_slist_free_all(headers);
curl_easy_cleanup(curl);
return res == CURLE_OK ? 0 : 1;
}
const BASE_URL = "http://127.0.0.1:5110";
const CLIENT_API_KEY = "...";
const payload = {
phone_id: "phone_main",
channel: "sms",
to: "+15551234567",
text: "hello",
source: "node",
idempotency_key: "sms-001",
};
const r = await fetch(`${BASE_URL}/api/v1/messages`, {
method: "POST",
headers: {
"Authorization": `Bearer ${CLIENT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
console.log(r.status, await r.json());
package main
import (
"bytes"
"fmt"
"net/http"
)
func main() {
base := "http://127.0.0.1:5110"
apiKey := "..."
body := []byte(`{"phone_id":"phone_main","channel":"sms","to":"+15551234567","text":"hello","source":"go","idempotency_key":"sms-001"}`)
req, _ := http.NewRequest("POST", base+"/api/v1/messages", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+apiKey)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { panic(err) }
fmt.Println(resp.Status)
}
import java.net.URI;
import java.net.http.*;
public class Send {
public static void main(String[] args) throws Exception {
String base = "http://127.0.0.1:5110";
String apiKey = "...";
String json = "{\"phone_id\":\"phone_main\",\"channel\":\"sms\",\"to\":\"+15551234567\",\"text\":\"hello\",\"source\":\"java\",\"idempotency_key\":\"sms-001\"}";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(base + "/api/v1/messages"))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpResponse<String> resp = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());
System.out.println(resp.body());
}
}
using System.Net.Http.Headers;
using System.Text;
var baseUrl = "http://127.0.0.1:5110";
var apiKey = "...";
var json = "{\"phone_id\":\"phone_main\",\"channel\":\"sms\",\"to\":\"+15551234567\",\"text\":\"hello\",\"source\":\"csharp\",\"idempotency_key\":\"sms-001\"}";
using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
var resp = await client.PostAsync(baseUrl + "/api/v1/messages", new StringContent(json, Encoding.UTF8, "application/json"));
Console.WriteLine((int)resp.StatusCode);
Console.WriteLine(await resp.Content.ReadAsStringAsync());