Billing
Saperly is prepaid — you top up a balance and usage meters against it through a reserve → settle → release ledger that can never overspend a balance or a scoped key's spend cap.
Saperly is prepaid: you top up a balance and every action meters against it. The Ledger does a reserve → settle → release cycle, guarded so you can never overspend your balance or a scoped key's spend cap — even for in-flight spend. All balances and amounts are integer cents.
Base URL https://api.saperly.com. Authenticate with Authorization: Bearer sap_sk_live_...; the workspace is resolved from the token.
Reserve → settle → release
Every metered action runs through three ledger moves:
- Reserve — before the action (placing a call, provisioning a number), funds are reserved. The reservation fails atomically if it would push the balance below zero or breach a key's spend cap, so an action that can't be paid for never starts.
- Settle — when the action completes, its actual cost is applied and any over-reserved amount is freed.
- Release — if the action never happens, the whole reservation is cancelled and the funds return to the balance.
A voice call is the canonical example: funds are reserved on POST /calls, then settled against the carrier-reported call duration when the call ends, with the remainder released. See Voice.
Transaction types
Every ledger move is recorded as a transaction. Amounts are integer cents.
| Type | Meaning |
|---|---|
reserve | Funds held for a pending action. |
settle | Actual cost applied when the action completes. |
release | A reservation cancelled; funds returned. |
topup | Funds added to the balance from your saved payment method. |
charge | A direct debit against the balance. |
adjust | A manual correction (credit or debit). |
Top-up and auto-recharge
- Top-up — add funds to your balance from a card on file.
- Auto-recharge — opt-in, off-session top-up that fires when your balance drops below a threshold. It is single-in-flight (one recharge runs at a time, so a burst of usage can't trigger duplicate charges) and pauses on SCA — if the card needs Strong Customer Authentication, auto-recharge halts until you complete it in the dashboard rather than silently failing.
Top-up and auto-recharge are managed in the dashboard
Funding your balance and configuring auto-recharge (threshold + recharge amount + payment method) live in the dashboard today. The API and SDKs are for reading balance and transactions and for the metered actions that draw against the balance.
Spend caps on scoped keys
A child API key can carry a spend cap, enforced at reserve time by the Ledger, so the cap holds even mid-action. On the wire (the POST /api-tokens body) it is two flat fields:
spendLimitCents?: number | null // hard cap, in cents
spendLimitResetPeriod?: 'monthly' | null // cap windowspendLimitResetPeriod: 'monthly'resets the counter at the UTC month boundary.spendLimitResetPeriod: null(or omitted) is a single lifetime cap.
Because the cap is checked when funds are reserved, a key can never overrun it part-way through a call — the reservation simply fails with SpendLimitExceeded (402). See Authentication for how an admin-scoped key attaches a spend cap when it mints a child key.
Number rent
Each number's monthly rent is swept automatically against your prepaid balance. The sweep is idempotent per (number, billing period), so a number is never double-billed for a month. Releasing a number stops the rent — see Numbers.
Pricing
Quote a number's price before you provision with GET /pricing/quote — both
country and numberType are required:
curl "https://api.saperly.com/pricing/quote?country=US&numberType=local" \
-H "Authorization: Bearer $SAPERLY_API_KEY"import { configure, pricing } from '@trysaperly/sdk'
configure({ apiKey: process.env.SAPERLY_API_KEY! })
const { data, error } = await pricing.quote({
query: { country: 'US', numberType: 'local' },
})
if (error) {
// typed error body — handle it
} else {
console.log(data.customerMonthlyCents, data.customerUpfrontCents)
}The quote returns your price:
| Field | Type | Notes |
|---|---|---|
customerMonthlyCents | number | Your monthly price. |
customerUpfrontCents | number | Your one-time provisioning price. |
See Numbers for provisioning.
Reading your balance
Your current prepaid balance rides on the usage summary (GET /usage),
alongside call and message totals. All amounts are integer cents.
import { configure, usage } from '@trysaperly/sdk'
configure({ apiKey: process.env.SAPERLY_API_KEY! })
const { data, error } = await usage.summary()
if (!error) {
console.log('balance (cents):', data.balanceCents)
console.log('calls:', data.calls.count, 'cost:', data.calls.totalCostCents)
console.log('messages:', data.messages.count)
}Transaction history lives in the dashboard
The API surfaces your live balance (on GET /usage) and the metered actions
that draw against it. The full ledger transaction history — every reserve /
settle / release / topup / charge / adjust — is shown in the dashboard.
Out-of-funds errors
When a reserve can't be covered, the action fails with a typed error (status 402) — handle it by topping up (or by raising the key's cap):
| Condition | Error | Status | Payload |
|---|---|---|---|
| Balance too low | InsufficientFunds | 402 | balanceCents, requestedCents |
| Scoped key's spend cap hit | SpendLimitExceeded | 402 | limitCents, priorSpendCents, requestedCents |
The SDK never throws by default — every method returns { data, error }. Branch on error and inspect its _tag to react:
import { configure, voice } from '@trysaperly/sdk'
configure({ apiKey: process.env.SAPERLY_API_KEY! })
const { data, error } = await voice.place({
body: { fromNumberId: 'num_...', to: '+15555550123' },
})
if (error) {
if (error._tag === 'InsufficientFunds') {
// Out of funds (402). Prompt a top-up, then retry.
console.error(
`Balance too low: ${error.balanceCents}¢ < ${error.requestedCents}¢ — top up to continue.`,
)
} else if (error._tag === 'SpendLimitExceeded') {
// This key is tapped out until its cap window resets.
console.error(`Spend cap hit: cap ${error.limitCents}¢.`)
}
}See Errors & idempotency for the full error model.
Related
- Authentication — the spend cap on scoped keys, enforced at reserve time.
- Numbers — provisioning, pricing, and monthly rent.
- Voice — reserve → settle on a live call.
- Errors & idempotency — handling
InsufficientFundsandSpendLimitExceeded. - Core concepts — the prepaid ledger in the Saperly model.
Compliance
Consent, disclosures, and 10DLC are first-class in Saperly and enforced independently of any LLM — outbound contact without recorded consent is blocked before it goes out.
Numbers
A number is a real phone number provisioned under your workspace and bound to one connection — provision, list, assign a handler, set a per-number webhook, and release, all over scoped sk_ keys.