A voice broadcast dials a list of contacts and puts a live AI agent on every answered call. Not a recording blasted at a thousand phones — a conversation, personalised per contact, that can answer questions, take a decision, and write the outcome back to your systems. You define the campaign once: which agent speaks, what it says to each contact, when it is allowed to dial, and how it retries the people it missed. Broadcasts handle the rest — pacing, retries, voicemail detection, and a per-contact report at the end.
Payment reminders, collections follow-ups, appointment confirmations, outage notifications, renewal calls, event reminders: anything that today means a human working down a spreadsheet, or an SMS that never gets read.
A broadcast runs a published AI agent against a contact list. The agent brings the conversational ability — the voice, the tools, the judgement about when to end a call. The broadcast brings the campaign machinery: the schedule, the retry policy, the per-contact personalisation, and the report.
prompt_template is composed on top of the agent's base_prompt for every call, with {{variable}} placeholders filled per contact. start_phrase is the exact opening line the agent speaks when the contact answers — script it, so every call starts identically and identifies you.
const res = await fetch("https://api.sautikit.com/v1/broadcasts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SAUTIKIT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "July payment reminders",
agent_id: process.env.REMINDER_AGENT_ID, // must be a published agent
caller_number_id: process.env.CAMPAIGN_NUMBER_ID,
prompt_template:
"Remind {{name}} that their instalment of KES {{amount}} is due on {{due_date}}. If they can pay, confirm when. If not, capture a promise-to-pay date. Be brief and courteous.",
start_phrase:
"Hello, this is Wema Credit calling with a reminder about your account.",
schedule_days: [1, 2, 3, 4, 5], // Mon-Fri
daily_start_minute: 540, // 09:00
daily_end_minute: 1020, // 17:00
timezone: "Africa/Nairobi",
max_attempts: 3,
backoff_minutes: 120,
retry_on: ["no_answer", "busy", "failed", "voicemail"],
}),
});
const broadcast = await res.json();The calling window matters as much as the script. The dialer will not place a call outside daily_start_minute to daily_end_minute in your timezone, or on days missing from schedule_days — a contact queued at 16:59 on Friday who cannot be reached rolls to Monday morning, not to Friday night.
Contacts carry the variables the template needs. Append them as JSON — one bad row is rejected individually and never sinks the batch — or upload a CSV in the dashboard for the same result.
await fetch(
`https://api.sautikit.com/v1/broadcasts/${broadcast.id}/contacts`,
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SAUTIKIT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
contacts: [
{
phone: "+254712345678",
variables: { name: "Alice", amount: "4,500", due_date: "Friday 25th" },
},
{
phone: "+254700000001",
variables: { name: "Ben", amount: "12,000", due_date: "Friday 25th" },
},
],
}),
},
);await fetch(`https://api.sautikit.com/v1/broadcasts/${broadcast.id}/start`, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.SAUTIKIT_API_KEY}` },
});/pause stops dialing immediately and /resume picks the queue back up, so a wrong script caught after fifty calls stays a fifty-call mistake. /duplicate clones a finished campaign — same agent, template, and schedule — for next month's run.
max_attempts and backoff_minutes govern the redial loop, and retry_on says which outcomes earn another attempt: no_answer, busy, failed, and voicemail. Voicemail handling is detection plus retry — when a machine answers, the dialer marks the attempt voicemail and schedules the next try inside your calling window. Your reminder reaches a person or it does not count as reached; it is never read out to an answering machine.
Every answered call is the full agent experience. The contact can interrupt mid-sentence and be heard. They can ask questions — "which instalment is this?" — and the agent answers from the context the template gave it. And because agents carry HMAC-signed HTTP tools against your endpoints, the call can do things: check the live balance before quoting it, capture a promise-to-pay date directly into your collections system, or reschedule the appointment it was calling to confirm. The agent ends the call itself when the conversation is done, and every call is capped by the agent's max_call_minutes.
That difference shows up in the outcomes. A robocall delivers audio; an agent call comes back with a result per contact — paid, promised Thursday, disputed, asked not to be called again.
POST /v1/agents/{id}/send places a single ad-hoc call through the same composition path — the agent's base_prompt plus your prompt_template and variables — without any campaign machinery. Call your own phone, hold the conversation, and tune the script before the list ever hears it.
await fetch(`https://api.sautikit.com/v1/agents/${AGENT_ID}/send`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SAUTIKIT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
to: "+254712345678", // your own number
prompt_template:
"Remind {{name}} that their instalment of KES {{amount}} is due on {{due_date}}.",
variables: { name: "Alice", amount: "4,500", due_date: "Friday 25th" },
}),
});GET /v1/broadcasts/{id}/report.csv returns the campaign snapshot — queued, attempted, reached, and per-contact outcomes — ready for a spreadsheet or your BI pipeline. For any individual call, GET /v1/calls/{id}?transcript=1 has the per-turn transcript with tool calls, so "what exactly did the agent promise Mrs. Otieno" is a lookup, not a guess. Calls are recorded by default.
Campaign calls bill like any other agent call: the standard per-second outbound call rate plus KES 4.00 per minute of AI time, metered per second. A 35-second confirmed reminder costs 35 seconds of each — unanswered attempts and voicemail detections do not burn AI minutes talking to nobody. Concurrency seats pace the dialer: the free tier runs 1 concurrent AI call, paid tiers 5, 8, or 10. The wallet is prepaid via M-Pesa, so a campaign can never spend more than the balance you loaded — a broadcast that hits an empty wallet pauses instead of overdrafting.