sautikit
PricingDevelopersBlogAbout
Sign inStart building
Guides
  • Browser Calling with WebRTC
  • Build a Call Center with Conferences
  • Build a Voice IVR
  • Claim and Route a Number
  • Quickstart: Place a Call
  • Record and Stream to S3
  • Verify Webhook Signatures
Concepts
  • Browser Calling with WebRTC
  • Calls
  • Phone Numbers
  • Voice Actions DSL
  • Wallet and Billing
  • Webhooks
  • Workspaces
Voice Actions
  • Conference
  • Dial
  • GetDigits
  • Hangup
  • Play
  • Record
  • Redirect
  • Reject
  • Say
Webhooks
  • call.answered
  • call.completed
  • call.failed
  • call.recording.ready
  • call.started
  • number.provisioned
  • number.released
  • storage.tier_changed
  • wallet.low_balance
  • wallet.top_up
SDKs
  • Go SDK
  • Node.js SDK
  • PHP SDK
  • Python SDK
Errors
  • account.suspended
  • accounts.admin_required
  • accounts.last_owner
  • accounts.remove_denied
  • accounts.role_change_denied
  • accounts.write_denied
  • api_key.already_revoked
  • api_key.expired
  • api_key.invalid
  • api_key.name_required
  • api_key.not_found
  • api_key.revoked
  • api_key.scope_denied
  • api_key.scope_invalid
  • audit.invalid_action_kind
  • auth.admin_required
  • auth.cannot_unlink_only_auth
  • auth.email_taken
  • auth.google_exchange_failed
  • auth.google_not_configured
  • auth.google_state_mismatch
  • auth.invite_email_mismatch
  • auth.no_workspace
  • auth.session_invalid
  • auth.token_expired
  • auth.token_invalid
  • auth.token_used
  • auth.workspace_forbidden
  • calls.events_failed
  • calls.get_failed
  • calls.list_failed
  • calls.not_found
  • calls.recording_expired
  • calls.recording_failed
  • calls.recording_not_found
  • calls.stats_failed
  • currency.unsupported
  • idempotency.conflict
  • internal_error
  • invitations.not_found
  • invite_requires_login
  • method_not_allowed
  • mpesa.unavailable
  • not_found
  • numbers.already_claimed
  • numbers.invalid_routing_url
  • numbers.not_assigned
  • numbers.not_found
  • numbers.price_missing
  • numbers.retired
  • numbers.series_invalid
  • numbers.series_not_active
  • numbers.sip_config_unavailable
  • numbers.sip_credentials.already_revoked
  • numbers.sip_credentials.not_found
  • numbers.sip_token_unavailable
  • numbers.suffix_collision
  • numbers.wallet_unavailable
  • paystack.unavailable
  • pbx.body_read_failed
  • pbx.ingest_failed
  • pbx.payload_decode_failed
  • pbx.resolve_failed
  • pbx.unknown_workspace
  • provider.has_active_series
  • provider.invalid_credentials
  • storage.tier_invalid
  • storage.tier_switch_failed
  • topup.not_found
  • validation.bad_request
  • validation.invalid_cursor
  • wallet.adjustment_invalid_kind
  • wallet.alert_subscriber_exists
  • wallet.alert_subscriber_invalid_email
  • wallet.alert_subscriber_not_found
  • wallet.alert_subscribers_max
  • wallet.currency_mismatch
  • wallet.insufficient_funds
  • wallet.invalid_amount
  • wallet.not_found
  • wallet.topup_below_minimum
  • webhooks.create_failed
  • webhooks.delete_failed
  • webhooks.deliver.secret_unset
  • webhooks.delivery_not_retryable
  • webhooks.dispatcher_unavailable
  • webhooks.event_unsupported
  • webhooks.list_failed
  • webhooks.not_found
  • webhooks.patch_failed
  • webhooks.rotate_secret_failed
Changelog
  • 2026-06-15: Public Pricing Endpoint
  • 2026-06-20: WebRTC SIP Token (HS256 5-min TTL + Key Rotation)
  • 2026-06-27: sautikit.com Launch
  • Inbound voice is now free; outbound drops to KES 3/min

Wallet and Billing

How Sautikit's prepaid KES wallet works: top-up, ledger entries, low-balance alerts, and trial credit.

2026-06-27

Next Steps

  • Quickstart: Place a CallFive steps from zero to a live outbound call on Sautikit. Sign up, add KES credit, claim a Kenyan DID, POST to /v1/calls/originate, and verify the signed webhook payload.
sautikit

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

Product

NumbersCalls & routingRecordingsWallet & billingPricing

Developers

DocumentationAPI referenceQuickstartAI promptChangelog

Company

AboutBlogCareersConsole

© 2026 Sautikit. All rights reserved.

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

Sautikit uses a prepaid wallet model denominated in Kenyan shillings (KES). Every charge (call cost, number rental, or storage) creates an immutable ledger entry. The wallet balance is a cached sum of all ledger entries. When the balance reaches zero, outbound calls are blocked and number rentals will not renew until you top up.

Wallet currency

Each workspace has exactly one wallet, locked to a single currency at creation. For workspaces registered in Kenya the currency is KES. The currency cannot be changed after the workspace is created.

All amounts in the API use minor units (lowest denomination). For KES: 1 KES = 100 minor units. An amount of 50000 minor units = KES 500.00.

Topping up

Top up via:

  • M-Pesa STK push (API-initiated): POST /v1/topups with provider: "mpesa" and a phone_e164. An STK push prompt arrives on that phone within seconds; the wallet credits as soon as M-Pesa confirms.
  • M-Pesa Paybill (manual): pay via the Paybill menu using your workspace's account reference (TS-XXXXXXXX). No API call needed. The platform reconciles the payment automatically from the M-Pesa callback.
# M-Pesa STK push: the most common path for in-Kenya teams.
curl -X POST "https://api.sautikit.com/v1/topups" \
  -H "Authorization: Bearer $SAUTIKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "mpesa",
    "amount_minor": 500000,
    "currency": "KES",
    "phone_e164": "+254722000001"
  }'

Why phone_e164 is required for M-Pesa: the STK push is Safaricom's "send a confirmation prompt to a specific SIM" protocol. Without a phone number there's no way to deliver the prompt. The number does NOT have to belong to a Sautikit user; one finance person can fund many workspaces from their own M-Pesa-registered phone.

The response includes a topup_id and mpesa_account_ref so your UI can also display the manual Paybill instructions as a fallback. The wallet balance updates automatically when M-Pesa confirms.

Ledger semantics

The wallet ledger is append-only. Every balance change (debit or credit) creates a new wallet_ledger entry with:

FieldDescription
kindThe type of transaction (see table below)
amount_minorSigned amount (positive = credit, negative = debit)
currencyAlways matches the workspace wallet currency
balance_after_minorWallet balance immediately after this entry

Ledger entry kinds:

KindDirectionTrigger
call_chargeDebitCall hangup (answered calls only)
number_rentalDebitMonthly number rental charge
topupCreditSuccessful top-up payment settlement
trial_creditCreditAdmin-provisioned trial credit (once per workspace)
admin_adjustmentEitherManual admin correction

You can list your ledger via GET /v1/wallet/statements for a full audit trail.

Balance check

curl "https://api.sautikit.com/v1/wallet/balance" \
  -H "Authorization: Bearer $SAUTIKIT_API_KEY"
{
  "workspace_id": "01900000-0000-7000-8000-000000000001",
  "balance_minor": 250000,
  "currency": "KES",
  "low_balance_threshold_minor": 100000
}

balance_minor is the cached value from the wallets table, updated atomically on each ledger write. It reflects the true balance within the consistency guarantees of a single PostgreSQL database.

Low-balance alerts

Set a threshold below which you want to receive a wallet.low_balance webhook event:

curl -X PATCH "https://api.sautikit.com/v1/wallet/threshold" \
  -H "Authorization: Bearer $SAUTIKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"threshold_minor": 100000}'

When the balance drops at or below the threshold during a call charge or rental, the platform emits a wallet.low_balance webhook event to any active subscriptions. The notification is rate-limited to once per 24 hours per workspace to avoid alert fatigue.

Suggested threshold for a workspace running 5 numbers: KES 500 (50000 minor units), enough for roughly 166 minutes of outbound calls at the standard rate (KES 3/min). Inbound calls are free and do not draw down the balance.

Alert subscribers

Beyond the wallet.low_balance webhook, you can configure email recipients who receive a notification when the wallet crosses your low-balance threshold. Useful when finance and engineering are different people.

Manage subscribers via the API:

# List current subscribers
curl https://api.sautikit.com/v1/wallet/alert/subscribers \
  -H "Authorization: Bearer $SAUTIKIT_API_KEY"
 
# Add a recipient
curl -X POST https://api.sautikit.com/v1/wallet/alert/subscribers \
  -H "Authorization: Bearer $SAUTIKIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email": "finance@your-company.co.ke"}'
 
# Remove a recipient
curl -X DELETE https://api.sautikit.com/v1/wallet/alert/subscribers/{subscriber_id} \
  -H "Authorization: Bearer $SAUTIKIT_API_KEY"

Add up to 20 recipients per workspace. Each receives a single email when the balance drops below the threshold (debounced, so you won't get spammed during a sustained low-balance period). Emails are sent to verified addresses only; the first POST triggers an opt-in confirmation that the recipient must accept before they receive alerts.

You can also manage subscribers via the console at Settings → Wallet → Alert recipients.

Low-balance behaviour

Balance stateBehaviour
Above zeroNormal; calls connect, numbers renew
At zeroOutbound calls return 402 wallet_insufficient_funds
Below zeroCan occur after the final call of a billing cycle; blocked until topped up
Renewal failureNumber moves to suspended status; service resumes on top-up

The balance can briefly go below zero by at most one call's cost, because the debit is applied at hangup rather than at call initiation.

Trial credit

New workspaces may receive a one-time trial credit provisioned by Sautikit support. The credit appears as a trial_credit ledger entry. Trial numbers provisioned alongside the credit have a trial_until expiry timestamp. When the trial ends, numbers auto-release and the remaining credit is not refunded.

Check whether your workspace has received trial credit:

curl "https://api.sautikit.com/v1/wallet/statements?kind=trial_credit" \
  -H "Authorization: Bearer $SAUTIKIT_API_KEY"

Next steps

  • Wallet API reference
  • Place your first call
  • Numbers and rental billing