SautiKit
/ai-agents/pricing/docs/api/blog
sign inStart building

AI receptionist: deploy an AI agent on your number in minutes

Bind a managed AI agent to your Sautikit number so every call is answered — always or after hours — with handover, transcripts, and recordings.

use-caseai-voice-agentai-agentsreceptionistinbound

Next Steps

  • Route Inbound Calls to an AI AgentBind a published AI agent to answer inbound calls on a number (always or after-hours), respond to the CallHandover callback when the agent hands back to a human, and chain specialist agents.
  • AI support voice agent: answer, look up, resolve — escalate with a summaryBind a managed AI agent to your support number with HMAC-signed tools that hit your API mid-call, and let handover brief your team with a summary. The BYO Stream recipe is the second half.
  • Voice broadcast campaigns: outbound AI calls at scaleCreate a broadcast on a published AI agent with per-contact templates, load contacts, and start. The dialer keeps to calling windows, retries no-answers and voicemail, and reports every outcome.
  • Your Sautikit number can now answer itself: managed AI agents are liveLaunch: bind a published AI agent to your Sautikit number and it answers callers in real time — interruptible, tool-calling, with a handover that briefs your team. The same agents dial broadcasts.
SautiKit

Programmable voice infrastructure for Africa. Buy numbers, place calls, and bill per second, all in local currency, via API.

All systems operational

Product

AI voice agentsBroadcastsNumbersCalls & routingRecordingsWallet & billingPricing

Developers

DocumentationAPI referenceVoice actionsWebhooksErrorsMCP serverQuickstartAI prompt

Compare

vs Africa's Talkingvs Twiliovs Infobipvs Vapi, Retell & BlandMigrate from Africa's TalkingAll comparisons

Company

AboutBlogConsole

© 2026 Sautikit. All rights reserved • Powered by Helloduty

Terms of ServicePrivacy Policy

Sautikit provides voice API services for application developers. Numbers provisioned on this platform are not configured for emergency calling (e.g. 999 / 112). Do not use Sautikit numbers as a replacement for a primary phone line.

Summary

An AI receptionist answers every inbound call, greets the caller by voice, understands what they want in natural language, answers the common questions, and hands off to a human only when it needs to. No hold music, no voicemail, no missed calls after hours.

Sautikit now runs this for you. AI agents are a managed product: you describe the receptionist in a prompt, publish it, and bind it to your number. Sautikit hosts the model, the voice, and the media plumbing — there is no server to run and no audio socket to terminate. Calls are answered in real time, the caller can interrupt mid-sentence, and when the agent decides a human is needed it hands the call to your team with a written summary of the conversation so far.

If you would rather bring your own model, the original build-it-yourself recipe — the Stream voice action forking live audio to your WebSocket — still works and is documented in the second half of this page.

Who this is for

  • SMEs and professional-services offices — a law firm, property agency, clinic, or accountancy — that cannot afford to miss an inbound call.
  • Teams expanding across Africa that need a front desk in every timezone without hiring one per office.
  • Ops teams that want an always-on receptionist live this week, not after a streaming-infrastructure project.
  • Developers who want the managed path for speed today and the option to swap in their own model later.

The fast path: a managed agent on your number

Three API calls stand between you and a working receptionist. All agent and routing endpoints take your API key as a Bearer token; the full request and response shapes live in the API reference.

1. Create the agent

The base_prompt is the receptionist's job description. Pick a model from the Sautikit catalog (GET /v1/models lists the ids, e.g. sautikit-flash), a voice, and a hard cap on call length.

const res = 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: "Front desk",
    base_prompt:
      "You are the receptionist for Acme Advocates in Nairobi. Greet callers, answer questions about office hours (Mon-Fri 8:00 to 17:30), location, and services, and collect a name and callback number for anything that needs a partner. Hand over to a human whenever the caller asks for one.",
    model: "sautikit-flash",
    voice: "Woman",
    max_call_minutes: 10,
  }),
});
 
const agent = await res.json();

2. Publish it

Only a published revision can answer calls, so drafts can be edited safely while a previous revision keeps working the phones.

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

mode: "always" puts the agent on every inbound call. handover_callback_url is where Sautikit sends the handover request when the agent passes a caller to your team.

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",
      handover_callback_url: "https://example.com/voice",
    },
  }),
});

Prefer the receptionist to cover only the hours your team does not? Use mode: "after_hours" with a schedule describing your business hours — the agent answers outside them, and calls inside them fall through to your normal routing:

{
  "inbound_agent": {
    "enabled": true,
    "agent_id": "9d2b1f53-8c0e-4f1d-9a6b-5d3a8c47e9f0",
    "mode": "after_hours",
    "schedule": {
      "timezone": "Africa/Nairobi",
      "hours": [{ "days": [1, 2, 3, 4, 5], "start": "08:00", "end": "17:30" }]
    },
    "handover_callback_url": "https://example.com/voice"
  }
}

The routing guide covers the schedule format, tri-state binding semantics, and every validation error.

What the caller experiences

The conversation is real-time and interruptible — a caller who talks over the agent is heard, and the agent stops and responds, the way a person would. The agent can call custom HTTP tools you attach to it (each request HMAC-signed, parameters defined by JSON Schema) to look things up in your systems mid-call, transfer to a specialist agent in the same session, or end the call itself when the caller is done. Every call is capped by max_call_minutes (default 15), so no conversation can run the meter indefinitely.

If no AI seat is free or the bound agent is unpublished, the call falls through to the number's normal routing. Callers never hear an error.

Handover briefs your team

When the agent decides a human is needed — the caller asked, or the conversation went beyond its brief — Sautikit POSTs a CallHandover request to your handover_callback_url with the agent's reason and a summary of the conversation so far:

{
  "sessionId": "HD_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "callerNumber": "+254700000001",
  "status": "CallHandover",
  "handover": {
    "agent_id": "9d2b1f53-8c0e-4f1d-9a6b-5d3a8c47e9f0",
    "agent_name": "Front desk",
    "reason": "caller asked to speak to a human",
    "summary": "Caller wants to book a consultation about a land matter; available Thursday afternoon; callback number confirmed."
  }
}

You respond with ordinary JSON voice actions — typically a dial to your team — and the human picks up a call they have already been briefed on:

{
  "actions": [
    { "say": { "text": "Connecting you to the front desk now." } },
    { "dial": { "number": "+254700000001" } }
  ]
}

QA every call

Calls are recorded by default, and every agent call produces a per-turn transcript — who said what, when, where the caller interrupted, and which tools the agent called:

const call = await fetch(
  `https://api.sautikit.com/v1/calls/${CALL_ID}?transcript=1`,
  { headers: { Authorization: `Bearer ${process.env.SAUTIKIT_API_KEY}` } },
).then((r) => r.json());

Read the transcripts of the first week's calls and tighten the base_prompt where the agent wandered — publishing a new revision takes effect on the next call.

What it costs

  • AI time: KES 4.00 per minute, metered per second, on top of the standard per-second call rates. A 90-second answered call bills 90 seconds of AI time, not two rounded-up minutes.
  • The number: a dedicated local number is KES 116 per month, claimed instantly from the dashboard or API.
  • Concurrency: the free tier includes 1 concurrent AI call; paid tiers raise that to 5, 8, or 10.
  • Wallet: everything is prepaid and topped up via M-Pesa, so spend can never exceed the balance you loaded.
ℹ

The whole setup also works from an AI assistant: the hosted Sautikit MCP server exposes create_agent, publish_agent, and update_number_routing, so Claude can build and bind your receptionist in one conversation.

Prefer to run your own model?

The managed agent is the fast path, but it is not the only one. If you want your own model in control of the conversation — your prompt orchestration, your voice stack, your inference bill — Sautikit makes this a media problem, not a telephony problem. You attach a webhook to a number, return a <Stream> voice action, and Sautikit forks the live call audio to your WebSocket server as raw PCM. You relay that audio to any LLM voice model and send the synthesised reply back on the same socket. When the AI decides a human is needed, your flow returns a <Dial> to warm-transfer the caller.

How it works — the real-time loop

  1. A caller dials your Sautikit number. The number's routing_url points at your voice webhook.
  2. Sautikit POSTs the call details to that webhook. Your server responds with an XML <Response> containing a <Stream> action.
  3. Sautikit opens a WebSocket to the url in your <Stream>. Your server must advertise the audio.drachtio.org subprotocol on the handshake, or the connection is rejected.
  4. Sautikit forks the live caller audio down that socket as binary PCM frames (16-bit little-endian).
  5. You relay those frames to your LLM (Gemini Live, OpenAI Realtime, or self-hosted). The model transcribes, reasons, and generates a spoken reply.
  6. You send the reply back as PCM frames on the same socket. Sautikit plays them into the call. This is full-duplex: audio flows both ways at once, so the caller can interrupt.
  7. When the AI decides to escalate, your webhook flow returns a <Dial> to a human's number and the caller is warm-transferred.
ℹ

Use outputSamplingRate="16000" for AI agents. The wider 16 kHz band gives the model cleaner audio than the 8 kHz PSTN default, which noticeably improves transcription and voice quality.

✎

Stream ships today via the raw XML form only — return the <Stream> element in an application/xml response and Sautikit forwards it to the media layer unchanged. A native JSON stream action is on the roadmap; until then, use the XML form for real-time media forking.

1. The XML your webhook returns

When the number is dialled, your webhook replies with an application/xml body opening the media stream:

<Response>
  <Stream
    name="receptionist"
    url="wss://your-app.example.com/audio"
    track="both_tracks"
    connect="true"
    outputSamplingRate="16000"
    bidirectionalSamplingRate="16000"
    statusCallback="https://your-app.example.com/stream-status"
    statusEvents="stream-started stream-stopped stream-error" />
</Response>

track="both_tracks" forks both call legs so your model hears the caller and its own playback. connect="true" answers and holds the call leg for the fork's lifetime — without it the document ends immediately and the call hangs up. outputSamplingRate is the PCM rate Sautikit sends to your socket; bidirectionalSamplingRate is the rate you send PCM back at (Sautikit resamples it to the channel codec). Audio on the wire is 16-bit little-endian PCM.

2. The WebSocket bridge (Node.js)

Your server terminates the socket, relays PCM to your LLM, and pipes the model's PCM back. The one hard requirement: advertise the audio.drachtio.org subprotocol.

import { WebSocketServer } from "ws";
import { connectToLLM } from "./llm.js"; // Gemini Live / OpenAI Realtime / self-hosted
 
const wss = new WebSocketServer({
  port: 8080,
  handleProtocols: () => "audio.drachtio.org", // required by Sautikit
});
 
wss.on("connection", async (call) => {
  const llm = await connectToLLM({ sampleRate: 16000 });
 
  // Caller audio (binary PCM) -> LLM
  call.on("message", (frame, isBinary) => {
    if (isBinary) llm.sendAudio(frame);
  });
 
  // LLM audio (binary PCM) -> back into the call on the same socket
  llm.on("audio", (pcm) => call.send(pcm, { binary: true }));
 
  // When the model decides to escalate, close the stream so your
  // voice webhook flow can return the <Dial> below.
  llm.on("handoff", () => call.close());
});

3. Escalating to a human

When the AI hands off, end the stream and let your flow return a <Dial> to the human's number, warm-transferring the caller:

<Response>
  <Say>Connecting you to the front desk now.</Say>
  <Dial>+254700000001</Dial>
</Response>

BYO pricing notes

The inbound call leg is billed per second for as long as the call is live on the Sautikit platform. There is no separate Sautikit charge for opening the media stream or for the WebSocket round-trips, and no AI-minute charge on this path — your LLM and voice-model costs are billed by that provider on their own metering. Sautikit only moves the audio.

⚠

The AI holds the call open while it thinks and speaks, and per-second billing runs the whole time. Keep model latency low and end the stream promptly on hang-up or handoff so you are not paying for dead air.

Next steps

  • Route inbound calls to an AI agent: binding, after-hours schedules, and the full CallHandover contract.
  • AI agents: the managed product page — capabilities, tiers, and pricing.
  • Voice broadcast campaigns: the same agents dialling outbound at scale.
  • AI support agent use case: a managed agent with custom tools against your own API.
  • Voice actions concept: the full <Stream> attribute table for the build-your-own path.