Core concepts

Credit systems

Name your own credits, decide when they mint, and let the gateway spend them down.

Seams meters every call in money. Your credit system is the product vocabulary on top: what one credit means, who gets how many, and what happens when they run out. Everything below is scoped to one application, the active sk_, not shared across products.

Two paths: wallet top-up vs plan allotment

These are different nouns. Mixing them up is the usual way to mis-price a pack or attach the wrong unit.

Wallet top-upPlan allotment
What it isMoney on the end user's walletAn entitlement pool minted from a bundle include
Unit on the wireAlways micros (dollars on the CLI)micros or credits, set on the include
How you mintseams users grant / grants.issueseams bundles include …
Tied to a bundle?No. Optional --bundle only picks who gets creditedYes. The include lives on that plan
When it refillsWhen you grant again (checkout, support, trial)Each period, daily, or once, per the include

grants.issue has no unit field and never credits a named-credit pool. If your UI sells “credits” as a wallet pack, convert to micros at a fixed rate before you call it.

CLI: top up a wallet

Amount is dollars. Recipients are named one way: specific end users, everyone on a bundle, or everyone in the application.

seams users grant 20.00 --user ada --reason top_up

# --bundle here means "everyone on pro", not "credit in pro's unit"
seams users grant 5.00 --bundle pro --reason apology
seams users grant 2.00 --all --reason welcome

CLI: attach an allotment to a plan

The include is the template. Subscribing an end user to that bundle opens the pool; the gateway spends entitlement pools before the money wallet.

seams bundles include pro monthly-credits \
  --name "Monthly credits" --unit credits --amount 500

# optional pack sold separately (once), still defined on the bundle
seams bundles include pro pack-500 \
  --name "500 credits" --unit credits --amount 500 \
  --price 5.00 --no-recurring

Named credit currencies

A named credit currency is defined once for your organization, then attached to a bundle. One bundle sells in at most one named currency; several includes on that bundle (monthly allotment, bonus, purchasable pack) share it. Across bundles you choose: reuse the same currency everywhere, or give each plan its own.

SetupWhat it means
Share one currencystarter and pro both point at the same unit, one “credit” means the same spend everywhere
Different per planEach bundle has its own unit (or its own value override), “credit” on Pro can be worth more than on Free
Several includes, one currencyOne bundle can mint many allotments; they all burn in that bundle’s currency
Money beside creditsThe wallet is always micros and independent of the plan currency; top-ups never inherit the bundle’s unit

The meter still prices usage in sell-micros. Named credits convert with the rate on the bundle (or the currency’s default). Customers should never see micro-dollars.

What you design

ChoiceWhat it decides
UnitDollars on the wallet, or a named currency on the plan
MintAt sign-up, on a plan period, after a pack purchase, or by support
Spend orderPlan allotments first, then wallet top-ups, then overage if allowed
CeilingA key or bundle cap so a loop cannot empty the account
Empty stateA 402 your product turns into top-up, wait, or upgrade

UI credits that grant money

When you sell “credits” but land them on the wallet, pick one rate and keep it in one module. The portal can show dollars; your product shows credits.

typescript
// 1 credit = $0.01 of usage → micros = credits × 10_000
const MICROS_PER_CREDIT = 10_000n

export function grantCredits(userId: string, credits: number, paymentId: string) {
  return seams.grants.issue({
    endUsers: [userId],
    amountUnits: String(BigInt(credits) * MICROS_PER_CREDIT),
    reason: `pack:${credits}`,
    idempotencyKey: paymentId,
  })
}

When the balance is gone

CodeWhat your product should do
insufficient_creditOffer a pack, upgrade, or wait for the next period
spend_limit_exceededStop the run; the period or key cap is the limit, not the wallet

Pass your payment event id as idempotencyKey on every grant after checkout. Retries must not mint twice.