If you are building a voice agent, the fork in the road used to be: use a managed AI-voice platform like Vapi, Retell AI, or Bland, or build on a programmable voice layer and bring your own model. Sautikit removes the fork. It ships managed AI agents as first-class API objects — create one, publish it, bind it to a number or point it at a contact list — and it remains the open telephony layer that forks raw call audio to any model you run yourself. This post lays out both paths.
TL;DR
- Vapi, Retell AI, and Bland are managed voice-agent platforms: STT, LLM, TTS, and telephony bundled behind one USD-billed API.
- Sautikit covers both sides. Managed AI agents answer inbound calls on your number and run outbound broadcasts — create with
POST /v1/agents, publish, bind, billed per second in KES from a prepaid M-Pesa wallet. And thestreamverb still forks live audio to your own WebSocket when you want to run the model yourself.- Choose Sautikit for a production agent on a Kenyan number: instant local numbers from KES 116, KES billing with no card, human handover with a written summary, and a bring-your-own-model path on the same platform when you want full control.
Vapi, Retell AI, and Bland are managed AI-voice-agent platforms. You configure an agent (system prompt, voice, tools) and the platform orchestrates speech-to-text, the model turn, and text-to-speech, then hands you telephony on top. They bill in USD, typically per-minute at a blended rate that folds in model and voice costs.
Sautikit is a programmable voice API that covers both sides of the old build-or-buy fork.
Path 1 — managed agents. POST /v1/agents with a name, a base prompt, a model from Sautikit's catalog, and a voice; publish an immutable revision; bind it to a number (every call, or only outside your business hours) or aim it at a contact list as a broadcast. Sautikit runs the whole conversational loop: the caller can interrupt the agent mid-sentence, the agent can call your own HTTP endpoints as JSON-Schema tools with HMAC-signed requests, transfer the caller to a specialist agent in-session, or hand the call to a human — with a written reason and conversation summary delivered to your webhook before anyone picks up. Recording is on by default, and per-turn transcripts are one GET /v1/calls/{id}?transcript=1 away.
Path 2 — your own model. Fork live call audio to your own WebSocket with the stream verb and wire in whatever model you like: your STT, your LLM, your TTS, your cost structure.
Billing for both paths is KES, prepaid, topped up over M-Pesa. Numbers activate instantly from KES 116.
The difference from the bundlers is that neither path is forced on you. A managed platform decides the pipeline and prices it as one blended USD number. Sautikit gives you a managed agent priced as two readable KES line items — call time plus AI time — and, on the same number, the option to take the raw audio and run the pipeline yourself.
| Dimension | Vapi / Retell / Bland | Sautikit |
|---|---|---|
| Managed AI agents | Bundled STT + LLM + TTS pipeline | First-class API objects: create → publish → bind or broadcast |
| Bring your own model | Pipeline choices set by the platform | Raw PCM via the stream media fork to your WebSocket |
| Pricing model | Blended USD per-minute | KES per second: standard call rate + KES 4.00/min AI time |
| Human handover | Platform-dependent | CallHandover webhook with the agent's reason + conversation summary |
| Telephony | Bundled on top | Native: instant local numbers from KES 116, JSON voice actions |
| Billing | USD, card | KES, prepaid wallet, M-Pesa STK top-up |
A Sautikit agent goes from POST /v1/agents to answering a live Kenyan number in an afternoon — and when you need to see and control every layer, the same platform hands you the raw audio. Either way, the invoice is in shillings.
Currency and payments. USD billing means FX exposure and a card requirement. Sautikit bills in KES from a prepaid wallet you top up over M-Pesa: no card, no dollar invoice. See /pricing for the current source of truth.
Cost opacity. A blended USD per-minute rate hides what you actually pay for. Sautikit itemises: the call itself at standard per-second rates, plus KES 4.00 per minute of AI time — also metered per second — while the managed agent is on the line. Same wallet, two line items you can read. On the bring-your-own-model path, the AI line moves to your model provider, paid directly.
A ceiling on control. On a bundler, outgrowing the platform's pipeline means migrating providers. On Sautikit the two paths live on the same number: start on managed agents, and if you ever need a custom pipeline, return a stream action and run your own model. No number port, no new wallet, no migration project.
Create the agent, publish an immutable revision, bind it to your number. Model and voice ids come from Sautikit's catalog at GET /v1/models.
// 1) Create the agent
const created = await fetch("https://api.sautikit.com/v1/agents", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SAUTIKIT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Reception",
base_prompt:
"You answer for Acme Ltd. Book appointments, answer product questions, and hand over to a human for refunds.",
model: "sautikit-flash", // list ids and voices with GET /v1/models
voice: "Woman",
}),
});
const agent = await created.json();
// 2) Publish an immutable revision
await fetch(`https://api.sautikit.com/v1/agents/${agent.id}/publish`, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.SAUTIKIT_API_KEY}` },
});
// 3) Bind it to your number
await fetch(`https://api.sautikit.com/v1/numbers/${NUMBER_ID}/routing`, {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.SAUTIKIT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
inbound_agent: {
enabled: true,
agent_id: agent.id,
mode: "always", // or "after_hours" with a weekly schedule
handover_callback_url: "https://yourapp.example/voice",
},
}),
});The agent now answers the number. (Agent and broadcast endpoints are not covered by @sautikit/node yet — use fetch as shown.) With mode: "after_hours" and a weekly schedule, your team keeps calls during business hours and the agent covers nights and weekends. When the agent decides a human should take over, Sautikit POSTs a CallHandover request to your handover_callback_url with the agent's reason and a summary of the conversation, and you reply with ordinary JSON voice actions — typically a dial to your team. The AI briefs your team before a human picks up. If every AI seat is busy or the agent is unpublished, calls fall through to your normal routing; callers never hear an error.
Outbound is the same object pointed outward: POST /v1/agents/{id}/send places a one-off agent call, and POST /v1/broadcasts runs a campaign:
{
"agent_id": "9d2b1f53-8c0e-4f1d-9a6b-5d3a8c47e9f0",
"name": "July appointment reminders",
"prompt_template": "Remind {{name}} about their {{date}} appointment.",
"start_phrase": "Hi, this is a reminder from your clinic.",
"schedule_days": [1, 2, 3, 4, 5],
"daily_start_minute": 540,
"daily_end_minute": 1020,
"timezone": "Africa/Nairobi",
"max_attempts": 3,
"backoff_minutes": 60,
"retry_on": ["no_answer", "busy", "voicemail"]
}Upload a CSV of contacts, start it, and collect a report.csv when it finishes. For the full inbound walkthrough — binding, after-hours scheduling, handover handling, specialist transfers — follow the route-inbound-calls guide.
When you want to own every layer, the pattern is: place or receive a call, return a <Stream> action that forks audio to your WebSocket, and run your own AI loop on the socket. Start by placing a call.
const res = await fetch("https://api.sautikit.com/v1/calls", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SAUTIKIT_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({ from: "+254700000000", to: ["+254711111111"] }),
});
const { call_id, status, stream_url } = await res.json();
console.log(call_id, status); // "HD_..."-backed session, "ringing"Your voice callback returns the <Stream> action as raw XML, forking both tracks to your WebSocket at 16 kHz:
import express from "express";
const app = express();
app.post("/voice", (req, res) => {
res.type("application/xml").send(
`<Response>
<Stream name="agent"
url="wss://your.ws/audio"
track="both_tracks"
connect="true"
outputSamplingRate="16000"
bidirectionalSamplingRate="16000"
statusEvents="stream-started stream-stopped stream-error" />
</Response>`
);
});
app.listen(3000);Now run the AI on the socket. Your WebSocket server must accept the audio.drachtio.org subprotocol; it receives binary PCM frames and plays audio back by sending binary PCM on the same socket. That return path is where you drop in Gemini or OpenAI.
import { WebSocketServer } from "ws";
const wss = new WebSocketServer({
port: 8080,
handleProtocols: () => "audio.drachtio.org",
});
wss.on("connection", (socket) => {
socket.on("message", async (frame, isBinary) => {
if (!isBinary) return; // control/status JSON
const reply = await runYourVoiceAI(frame); // Gemini/OpenAI -> PCM
socket.send(reply, { binary: true }); // play back into the call
});
});That runYourVoiceAI function is the point of this path: it is your STT, your LLM, your TTS, chosen and tuned by you. For a full realtime build, see the AI voice agent pillar and the Gemini realtime flagship.
Reach for the managed path when the goal is a production agent on a Kenyan number, fast: create → publish → bind is an afternoon of work, the number underneath activates instantly, handover keeps humans in the loop with a written summary, and the whole thing bills per second in KES from the wallet you already top up over M-Pesa. The free tier includes 1 concurrent AI call, so the first working agent costs you call time and pennies of AI time; paid tiers run 5, 8, or 10 concurrent.
Reach for the bring-your-own-model path when you want full control of the pipeline: a specific model, a custom STT stack, your own latency budget. You keep the same numbers, the same wallet, and pay your model provider directly.
Either way, your AI tooling can drive the result: the hosted MCP server exposes the whole stack — numbers, calls, create_agent, update_number_routing, broadcasts — to Claude or any MCP client, with human confirmation on anything that spends money. A USD bundler can also get a demo up the same day, but you inherit its pipeline choices, card billing, FX exposure, and a per-minute platform fee stacked on top of telephony.
If your product also needs SMS, WhatsApp, or an agent desk beside the voice agent, keep them in one family: Helloduty is the multi-channel CX platform Sautikit plugs into, so voice stays focused while the rest of the channels live next door.
No. Managed AI agents run the entire conversational loop — you supply a prompt, pick a model and voice from Sautikit's catalog, and bind the agent to a number. The stream verb remains for teams that want to bring their own pipeline instead.
Yes, on the bring-your-own-model path: the stream verb forks raw PCM to your WebSocket and you wire in Gemini, OpenAI, or a self-hosted model. Managed agents run on models from Sautikit's own catalog, listed at GET /v1/models.
Managed platforms charge a blended USD per-minute rate covering model and voice. A Sautikit managed agent bills the call at standard per-second rates plus KES 4.00 per minute of AI time, metered per second, from the same prepaid KES wallet — two visible line items, no card. On the bring-your-own-model path you pay Sautikit for the call and your model provider directly.
Yes — it is a built-in tool. When the agent decides a person should take over, Sautikit POSTs a CallHandover webhook with the agent's reason and a summary of the conversation so far, and you respond with ordinary JSON voice actions (a dial to your support line, an enqueue, anything the DSL allows). Your team starts the conversation already briefed.
The call falls through to the number's normal routing — your voice callback, your IVR — and the caller never hears an error. The free tier includes 1 concurrent AI call; paid tiers run 5, 8, or 10.
Sautikit is Kenya-first and expanding to more markets. M-Pesa top-up and instant local numbers reflect where we operate today; the API is the same wherever you build.
<Stream> action and connect your own model on the WebSocket.Start with Sautikit → · See pricing → · Need SMS, WhatsApp & an agent desk? Helloduty →