Public HTTPS

HTTPS API: Send From Any Language

You send a single HTTPS request. The relay queues it. Your phone delivers it via the selected channel.

Auth

Header
Authorization: Bearer CLIENT_API_KEY
Content-Type
application/json
Keep CLIENT_API_KEY private. If it leaks, rotate/revoke it in the portal and reconfigure clients.
Base URL = your relay URL. Use the hosted relay URL shown on Setup QR / portal.

Send A Message

Method
POST
Path
/api/v1/messages
Request JSON
{
  "phone_id": "phone_main",
  "channel": "sms",
  "to": "+15551234567",
  "text": "hello",
  "source": "my-app",
  "idempotency_key": "my-unique-id-001"
}
Fields
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.
Channel Notes
Keep your credentials private. If you suspect compromise, rotate/revoke keys and reconfigure clients/phones.
email and tg_bot may be delivered server-side (and can return status: sent immediately) depending on relay configuration.
Response
// 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": "..." }
Quick Variants
// 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" }
For the web-to-web private channel, see: pChat docs.

Check Status

Method
GET
Path
/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
  }
}
List recent
GET /api/v1/messages?limit=50
Health
GET /health

Inbound Traffic + Callback API

Query traffic
GET/POST /api/v1/webchat/inbound/query
Manage callbacks
GET/POST/DELETE /api/v1/webchat/callbacks/inbound
Query Inbound Stats (POST)
curl -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
  }'
Response Shape
{
  "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
}
Create Callback With Filters
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
  }'
Callback payloads include matched inbound events and subscription details. Loop sources are auto-filtered by server defaults.

Examples (Copy/Paste)

curl (works everywhere)
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\"}"
Python (requests)
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())
C++ (libcurl)
#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;
}
Node.js (fetch)
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());
Go (net/http)
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)
}
Java (HttpClient)
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());
  }
}
C# (.NET HttpClient)
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());