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 sap_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 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.

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 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 window
  • spendLimitResetPeriod: '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:

FieldTypeNotes
customerMonthlyCentsnumberYour monthly price.
customerUpfrontCentsnumberYour 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):

ConditionErrorStatusPayload
Balance too lowInsufficientFunds402balanceCents, requestedCents
Scoped key's spend cap hitSpendLimitExceeded402limitCents, 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.

  • 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 InsufficientFunds and SpendLimitExceeded.
  • Core concepts — the prepaid ledger in the Saperly model.

On this page