Calls
How Sautikit models call legs, states, directions, inbound vs outbound flow, and recording lifecycle.
Every phone call in Sautikit is a calls record with a direction (inbound or outbound), a sequence of lifecycle states, and an optional recording. Call cost is calculated at hangup from the answered duration and debited from your KES wallet in a single ledger entry.
| Direction | Description |
|---|---|
inbound | A caller dials your Sautikit number; Sautikit routes it to your voice_callback_url |
outbound | Your application calls POST /v1/calls/originate; Sautikit dials the remote party |
For inbound calls Sautikit POSTs the call state to your voice_callback_url and your server responds with VoiceAction DSL verbs that control the call flow.
A call moves through states in order. Not every state is visited on every call.
| Status | Meaning |
|---|---|
ringing | The remote party is being alerted; no audio yet |
answered | Both parties are connected and audio is flowing |
completed | Call ended normally |
failed | A carrier or platform error prevented connection |
no_answer | Remote party did not answer within the timeout |
busy | Remote party returned a busy signal |
canceled | Caller hung up before the remote answered |
completed is the only fully successful terminal state. All others (failed, no_answer, busy, canceled) are terminal failure flavours. Your dashboard's "Failed today" stat counts all four.
The platform signs each webhook POST with X-Sautikit-Signature. See Webhooks for verification details.
You must own the from number and it must be in active status. The voice_callback_url may differ from the number's default routing, which is useful for per-campaign overrides.
Cost is calculated only on answered calls:
cost_minor = ceil(duration_seconds / 60) × rate_per_minute_minor
Where rate_per_minute_minor is the per-workspace KES rate at call time. A 90-second call is billed as 2 minutes. A call that never reaches answered costs nothing.
The debit is atomic: the platform writes a wallet_ledger entry and stamps ledger_entry_id on the call record in a single transaction. If the debit fails (insufficient funds), the call is still completed; the balance may briefly go negative by one call's cost. Calls are then blocked until the wallet is topped up.
Every call has:
calls row with the summary (direction, status, cost_minor, duration_seconds, etc.)call_events rows with granular telemetry (call.ringing, call.answered, etc.)Query calls via GET /v1/calls (supports filtering by status, direction, date range, and remote number substring).
Enable recording by including a Record verb in your VoiceAction response, or by setting record on a Dial verb.
Recordings are stored in the object storage bucket attached to your workspace's storage tier. The API never streams audio through the Go process; it returns a short-lived presigned URL. Default free storage is 1 GB. Recordings older than your retention period are pruned; the API returns 410 calls.recording_expired for those.