Wallet and Billing
How Sautikit's prepaid KES wallet works: top-up, ledger entries, low-balance alerts, and trial credit.
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.
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.
Top up via:
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.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.
The wallet ledger is append-only. Every balance change (debit or credit) creates a new wallet_ledger entry with:
| Field | Description |
|---|---|
kind | The type of transaction (see table below) |
amount_minor | Signed amount (positive = credit, negative = debit) |
currency | Always matches the workspace wallet currency |
balance_after_minor | Wallet balance immediately after this entry |
Ledger entry kinds:
| Kind | Direction | Trigger |
|---|---|---|
call_charge | Debit | Call hangup (answered calls only) |
number_rental | Debit | Monthly number rental charge |
topup | Credit | Successful top-up payment settlement |
trial_credit | Credit | Admin-provisioned trial credit (once per workspace) |
admin_adjustment | Either | Manual admin correction |
You can list your ledger via GET /v1/wallet/statements for a full audit trail.
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.
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.
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.
| Balance state | Behaviour |
|---|---|
| Above zero | Normal; calls connect, numbers renew |
| At zero | Outbound calls return 402 wallet_insufficient_funds |
| Below zero | Can occur after the final call of a billing cycle; blocked until topped up |
| Renewal failure | Number 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.
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"