---
title: >-
  Reach thousands of Kenyans with a bulk voice call API: KES pricing, M-Pesa,
  quickstart
description: >-
  Send bulk voice calls in Kenya with the Sautikit API: KES pricing, M-Pesa
  top-up, a copy-paste quickstart, and delivery tracking at scale.
summary: >-
  A practical guide to running bulk voice broadcasts in Kenya: what they cost in
  KES, how M-Pesa wallet top-up works, a runnable quickstart, and how to track
  answer rates at scale.
date: 2026-07-01T00:00:00.000Z
type: blog
---


If you have searched for "bulk SMS Kenya," you will have found a hundred providers. Search for a **bulk voice call API in Kenya** and the results thin out fast. Most platforms treat voice as an afterthought bolted onto SMS, priced in USD, billed to a card you may not have. [Sautikit](/) is the opposite: voice-only, billed in **KES**, topped up over **M-Pesa**, with a JSON API a backend engineer can wire up in an afternoon.

This guide covers what bulk voice costs in Kenya, how the wallet works, and a runnable quickstart for broadcasting to thousands of numbers.

> **TL;DR**
> - Outbound calls bill at **KES 3/min**, billed per second from the moment the call connects; a 20-second broadcast call is about **KES 1**.
> - Top up the wallet over **M-Pesa**; no USD card, no monthly minimum. A zero balance returns `402` before any call is placed.
> - Bulk = a loop over `POST /v1/calls`, one request per recipient, with an `Idempotency-Key` so retries never double-dial.

## What a "bulk voice call" actually is

There is no special "broadcast" endpoint to learn. A bulk campaign is just **many single calls**: you iterate your recipient list and call `POST /v1/calls` once per number. Each call dials immediately and returns a `call_id` before the remote party answers; you track outcomes through the `call.answered` and `call.completed` webhooks. Whether you play a pre-recorded greeting, a TTS message, or an interactive menu is decided by the **voice actions** you return when the call connects.

That design keeps the mental model simple: master one call, and you have mastered a million.

## What it costs in Kenya

| Item | Rate |
|---|---|
| Outbound call | KES 3 / minute |
| Billing increment | per second from the moment the call connects |
| Phone number | from KES 100 / month (ex. VAT; KES 116 incl. VAT) |
| Call recording | 1 GB storage included free; paid tiers from KES 599 / month |

A festive greeting blast of 20 seconds per call works out to:

```
20 seconds × (KES 3 / 60) = KES 1.00 per answered call
10,000 recipients × KES 1.00 = KES 10,000 per campaign
```

These are illustrative; [the pricing page](/pricing) is the single source of truth for current rates and the billing increment. The point that matters for budgeting: **you pay for answered talk-time in KES**, not for an SMS-style per-message fee, and unanswered calls do not bill the full leg.

## Step 1: Fund the wallet over M-Pesa

Sautikit runs on a **prepaid wallet**. Before any call leaves the PBX, a pre-flight check confirms your balance: a zero balance returns `402 wallet.insufficient_balance` and **no call leg is created**, so you never get a surprise invoice. Top up over M-Pesa in under a minute; the credit lands in KES and the wallet decrements per call. No card, no USD FX, no monthly commitment.

## Step 2: Place your first call

```js
// quickstart.js
const BASE = "https://api.sautikit.com";

const resp = await fetch(`${BASE}/v1/calls`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SAUTIKIT_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "demo-call-001",
  },
  body: JSON.stringify({
    from: process.env.SAUTIKIT_FROM, // a number your workspace owns
    to: ["+254700000001"],
  }),
});

console.log(resp.status, await resp.json());
// 202 { call_id: '9d2b1f53-…', pbx_session_id: 'HD_1a2b3c', status: 'ringing', ... }
```

A `202` means the PBX accepted the call and is dialing. The `status` is the live PBX state (`ringing`), not the final outcome; for that you poll `GET /v1/calls/{id}` or, better, subscribe to webhooks.

## Step 3: Say something when they answer

When the call connects, Sautikit POSTs to your `voice_callback_url`. Return the message to play:

```js
import express from "express";
const app = express();
app.use(express.urlencoded({ extended: true }));

app.post("/voice/greeting", (req, res) => {
  res.json({ actions: [
    { say: {
        text: "Hello from Acme. Habari kutoka Acme. Thank you for being our customer.",
        language: "sw-KE",
    } },
    { hangup: {} },
  ] });
});
```

Swap the `say` for a `getDigits` menu and you have an interactive broadcast: "press 1 to confirm," "press 2 for a callback." See the [voice actions reference](/developers/concepts/voice-actions) for the full verb set, and our [debt-reminder robocall guide](/blog/debt-reminder-robocall-mpesa-kenya) for a press-to-pay example.

## Step 4: Scale to the whole list

```js
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

function dial(recipient) {
  return fetch(`${BASE}/v1/calls`, {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.SAUTIKIT_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": `campaign42:${recipient.id}`, // one dial per recipient
    },
    body: JSON.stringify({ from: FROM_NUMBER, to: [recipient.msisdn] }),
  });
}

// Pace the campaign; don't fire 10,000 calls in one burst
for (const recipient of recipients) {
  dial(recipient); // fire without awaiting; the sleep paces throughput
  await sleep(50); // ~20 calls/sec; tune to your concurrency allowance
}
```

Two rules for bulk that save you grief:

- **Idempotency per recipient.** Key each call on a stable recipient ID so a re-run or partial failure never double-dials anyone (important for cost *and* for staying clear of harassment rules).
- **Pace the dialing.** Don't submit the entire list in one burst; throttle to a sustainable calls-per-second so answered calls and webhooks stay manageable.

## Step 5: Track delivery

Subscribe to the call lifecycle webhooks and tally outcomes:

```js
app.post("/webhooks/calls", express.json(), (req, res) => {
  const e = req.body;
  if (e.event === "call.completed") {
    recordOutcome(e.call_id, e.status); // answered / no-answer / busy / failed
  }
  res.sendStatus(204);
});
```

Your answer rate, no-answer rate, and (for interactive flows) DTMF-completion rate all come off this stream. That is your campaign report.

## Bulk voice vs bulk SMS in Kenya

SMS is unbeatable for one-way, low-urgency text: a receipt, a code, a link. Voice wins when you need **attention, interactivity, or reach beyond smartphones**: festive greetings that feel personal, payment reminders that can collect, confirmations that cut no-shows, and broadcasts to feature-phone or low-literacy audiences. The two are complements, not competitors.

If you need both channels from one account, that is where [Helloduty](https://helloduty.com), the multi-channel CX platform Sautikit is part of, comes in: voice through Sautikit, SMS and WhatsApp through Helloduty, one wallet and one set of contacts.

## FAQ

### Is there a bulk or broadcast endpoint?
No special endpoint; you loop `POST /v1/calls`, one request per recipient. This keeps the API small and the same code works for one call or a million.

### How do I pay if I don't have a USD card?
You don't need one. Sautikit bills in KES and you top up the prepaid wallet over M-Pesa. Calls draw down the balance; a zero balance blocks new calls with a `402` before any spend.

### What does a bulk voice campaign cost in Kenya?
Roughly KES 1 per 20-second answered call (KES 3/min, billed per second from the moment the call connects). A 10,000-recipient greeting blast is about KES 10,000 in talk-time.

### How fast can I dial?
Pace to a sustainable calls-per-second rather than bursting the whole list. Throttling keeps webhooks and answered calls manageable and avoids looking like spam to carriers.

### Can the call be interactive, not just a recording?
Yes. Return a `getDigits` action and you get DTMF menus ("press 1 to confirm"), including press-to-pay via M-Pesa STK push.

## Get started

1. [Create a Sautikit workspace](/) and claim a phone number (from KES 100/month ex. VAT).
2. Top up over **M-Pesa**: KES billing, no card.
3. Run the quickstart above and watch the `202` come back.

**[Start with Sautikit →](/)** &nbsp;·&nbsp; **[See pricing →](/pricing)** &nbsp;·&nbsp; **[Need SMS, WhatsApp & an agent desk? Helloduty →](https://helloduty.com)**

## Further reading

- [Calls API reference](/developers/api#calls): request/response, status values, idempotency
- [Voice actions reference](/developers/concepts/voice-actions): `Say`, `GetDigits`, `Dial`, `Record`
- [Debt-reminder robocall with M-Pesa press-to-pay](/blog/debt-reminder-robocall-mpesa-kenya)
- [Pricing: KES rates and number costs](/pricing)
