saperly
Guides

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 sk_live_...; the workspace is resolved from the token.

Reserve → settle → release

Every metered action runs through three ledger moves:

  1. 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.
  2. Settle — when the action completes, its actual cost is applied and any over-reserved amount is freed.
  3. 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 reported durationSec on end, with the remainder released. See Voice.

Transaction types

Every ledger move is recorded as a transaction. Amounts are integer cents.

TypeMeaning
reserveFunds held for a pending action.
settleActual cost applied when the action completes.
releaseA reservation cancelled; funds returned.
topupFunds added to the balance from your saved payment method.
chargeA direct debit against the balance.
adjustA 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 spendLimit, enforced at reserve time by the Ledger, so the cap holds even mid-action:

spendLimit?: {
  amountCents: number
  resetPeriod?: 'monthly' | null
}
  • resetPeriod: 'monthly' resets the counter at the UTC month boundary.
  • resetPeriod: 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. See Authentication for how a key with keys:admin attaches a spendLimit 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:

curl "https://api.saperly.com/pricing/quote?country=US&numberType=local" \
  -H "Authorization: Bearer $SAPERLY_API_KEY"
const res = await fetch(
  'https://api.saperly.com/pricing/quote?country=US&numberType=local',
  { headers: { Authorization: `Bearer ${process.env.SAPERLY_API_KEY!}` } },
)
const quote = await res.json()

The quote returns your price:

FieldTypeNotes
customerMonthlyCentsnumberYour monthly price.
customerUpfrontCentsnumberYour one-time provisioning price.

See Numbers for provisioning.

Reading balance and transactions

The published v1 SDK exposes your balance and ledger history:

// Current balance (cents)
const balance = await saperly.billing.balance()

// Paginated transactions
const page = await saperly.billing.transactions({ limit: 50, cursor })

Out-of-funds errors

When a reserve can't be covered, the action fails with a typed error — handle it by topping up (or by raising the key's cap):

Conditionv2 error (status)v1 SDK code (status)
Balance too lowInsufficientFunds (402)insufficient_credits / payment_method_required (402)
Scoped key's spend cap hitSpendLimitExceeded (402)agent_cap_exceeded (402)

The agent_cap_exceeded error carries spent_cents, cap_cents, and cycle_reset_at so you can tell the caller exactly when the cap frees up.

import { Saperly, InsufficientFundsError } from '@saperly/sdk'

try {
  await saperly.calls.place({ fromNumberId: 'num_...', to: '+15555550123' })
} catch (err) {
  if (err instanceof InsufficientFundsError) {
    // Out of funds (402). Prompt a top-up, then retry.
    console.error('Balance too low — top up to continue.')
  } else {
    throw err
  }
}

See Errors & idempotency for the full error model.

  • AuthenticationspendLimit on scoped keys, enforced at reserve time.
  • Numbers — provisioning, pricing, and monthly rent.
  • Voice — reserve → settle on a live call.
  • Errors & idempotency — handling InsufficientFunds and SpendLimitExceeded.
  • Core concepts — the prepaid ledger in the Saperly model.

On this page