Inbound & outbound calls
Place and receive calls over the REST API. Webhook-driven: your server tells us how to handle each call, as it happens.
API referenceFeatures
Everything you need to put phone calls inside your product — numbers, call control, softphones, recordings and live events — without running a telephony platform yourself.
Place and receive calls over the REST API. Webhook-driven: your server tells us how to handle each call, as it happens.
API referenceDrive calls with plain XML — Say, Play, Dial, Record, Pause, Redirect. No SDK to learn, no call state to manage yourself.
Verb referenceGive end-users a real phone inside your app. Mint short-lived phone tokens on your server — no secret key in the browser.
Browser SDKWatch calls live over a WebSocket. Namespaced event codes and stable schemas make integrations predictable, not guesswork.
Event codesSpeak dynamic text on a call with a single verb, and record calls to storage with signed, expiring download URLs.
Recordings APIA KES wallet and no monthly fees. Calls billed per second, numbers per month. Live balance monitoring stops runaway spend.
Talk to us about ratesCalls
Start an outbound call with a single POST. Inbound calls to your numbers arrive as a webhook to your server. Both directions are the same mental model: an HTTP request in, a call-control document back.
curl https://api.sautipbx.com/api/calls/originate \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "to": "+254711111111",
"from": "+254709080010",
"url": "https://yourapp.com/voice" }'import requests
res = requests.post(
"https://api.sautipbx.com/api/calls/originate",
headers={"Authorization": "Bearer sk_live_..."},
json={
"to": "+254711111111",
"from": "+254709080010",
"url": "https://yourapp.com/voice",
},
)
print(res.json()["call_id"])const res = await fetch(
"https://api.sautipbx.com/api/calls/originate",
{
method: "POST",
headers: {
Authorization: "Bearer sk_live_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
to: "+254711111111",
from: "+254709080010",
url: "https://yourapp.com/voice",
}),
},
);
const { call_id } = await res.json();Call control
Your webhook returns a short XML document and the platform executes the verbs in order. There is no call state to track on your side and no SIP stack to operate: every decision is just an HTTP response.
<!-- Your server's reply to our webhook -->
<Response>
<Say voice="female">Welcome to Acme.</Say>
<Play>https://cdn.acme.com/menu.mp3</Play>
<Record maxLength="30" />
</Response><Response>
<Say>Connecting you now.</Say>
<Dial timeout="25">+254722000000</Dial>
</Response>Real time
Stream every call transition over a WebSocket, and put a real softphone inside your own web app with the browser SDK. Tokens are minted server-side and short-lived, so your secret key never touches the browser.
const ws = new WebSocket(
"wss://api.sautipbx.com/api/stream?token=..."
);
ws.addEventListener("message", (e) => {
const ev = JSON.parse(e.data);
// call.ringing, call.answered, call.completed …
console.log(ev.type, ev.call_id, ev.reason_code);
});import { Phone } from "@sautipbx/voice-sdk";
// Your server mints a short-lived token, so the
// secret key never reaches the browser.
const phone = new Phone({ token });
await phone.register();
await phone.call("+254711111111");The quickstart takes you from an empty account to a real answered call.