Farther ShoreDocs
Go to Farther Shore
Design and operate commerce
Entitlements vs economics
Cohorts & releases
Plan transitions
Connect Stripe
Subscriptions & usage
Plan changes
Billing strategies
Free with a real boundFlat subscriptionPay as you goPrepaid walletSubscription plus included allowance and overageTrialOpaque allowances (5x / 20x)Tiered and dimension-dynamic pricingResource-priced productsDecision checklist
Pricing catalogs
Funding & allowances
Economic agreements
Commercial releases
Bill preview API
Usage & billing policy
Ledger & settlement
Subscription + overage
Freemium that converts
Add a trial
Add a spend cap
Change a price
Prepaid wallet
Meter AI tokens
commerce HTTP contracts
Status
Docs/Monetize/Billing strategies

Billing strategies

Pick the plan kind that matches what customers buy, and nothing more.

Start with the simplest economic model. Every plan declares its kind up front, and the compiler only accepts the controls that kind needs — so the shape you pick is the shape you get. Access (grants) and structural bounds (limits) sit beside every kind and are not repeated below. Any kind that bills usage must carry one of them — a limits rule, maxMonthlySpendCents, or spendPolicy: { onExhaustion: fs.exhaustion.block } — or the build fails with PLAN_UNBOUNDED_SPEND.

The examples share one measurement and one catalog:

ts
const units = fs.measure("units");
const usage = fs.meter("api_usage", { measures: [units] });
const usagePricing = fs.pricing("api_usage", {
  meter: usage,
  catalog: [fs.rate.perUnit(fs.money.usd(0.01))],
});

Free with a real bound

ts
fs.plan("free", {
  kind: fs.plan.kind.free,
  grants: [status],
  limits: [requests.perMinute(60)],
});

A free plan carries no economic controls. Bound it structurally: attach requests to the granted operations and give it a rate limit.

Flat subscription

ts
fs.plan("flat", {
  kind: fs.plan.kind.flat,
  price: fs.money.usd(30).monthly(),
});

Customers buy access, not a quantity. Do not invent a meter to justify the fee.

Pay as you go

ts
fs.plan("usage", {
  kind: fs.plan.kind.usage,
  usagePricing: usagePricing.current(),
});

Postpaid: every reported unit is rated at the bound catalog and settled on the invoice. Value scales directly with measured consumption. There is no funding control because nothing is prepaid; add spendPolicy: { rail: fs.rail.x402, onExhaustion: fs.exhaustion.block } only for a pay-per-call rail where each operation is funded before it runs.

Prepaid wallet

ts
fs.plan("prepaid", {
  kind: fs.plan.kind.prepaid,
  usagePricing: usagePricing.current(),
  funding: { buckets: [fs.prepaid(fs.money.usd(25), { topUp: true })] },
  spendPolicy: { onExhaustion: fs.exhaustion.block },
});

The subscriber buys $25 of rated value; usage draws it down; topUp: true lets the platform offer replenishment; the gateway blocks at zero (credit_exhausted, 402). Prepaid plans require every unbounded measure on their routes to declare maxOutputUnits, chunkPolicy, or a post-stream settlementMax so admission can reserve a finite economic maximum.

Subscription plus included allowance and overage

ts
fs.plan("hybrid", {
  kind: fs.plan.kind.hybrid,
  price: fs.money.usd(30).monthly(),
  usagePricing: usagePricing.current(),
  funding: { buckets: [fs.included(fs.money.usd(10))] },
  spendPolicy: {
    onExhaustion: fs.exhaustion.overage(usagePricing.current()),
  },
});

An included bucket is $10 of rated value reissued each period, not "N units": the allowance is worth the same across models and dimensions, and its cost is tracked as contra-revenue in the ledger. Overage after exhaustion is rated at the same catalog. Use fs.exhaustion.block only on prepaid and custom plans.

Trial

ts
fs.plan("trial", {
  kind: fs.plan.kind.trial,
  price: fs.money.usd(30).monthly(),
  lifecycle: { trialDays: 14 },
});

Obligation is suppressed during the trial; the recurring price applies after. Add usagePricing when post-trial usage should be rated.

Opaque allowances (5x / 20x)

ts
fs.plan("max-5x", {
  kind: fs.plan.kind.hybrid,
  price: fs.money.usd(100).monthly(),
  usagePricing: usagePricing.current(),
  funding: {
    buckets: [
      fs.included(fs.money.usd(25), {
        display: fs.display.multiplier({ factor: 5 }),
      }),
    ],
  },
  spendPolicy: {
    disclosure: fs.disclosure.opaque,
    onExhaustion: fs.exhaustion.overage(usagePricing.current()),
  },
});

disclosure.opaque makes every subscriber surface — including the bill preview API — show allowance remaining as display units or a fraction, never rates or dollar totals. Rating stays exact underneath, so the ledger reconciles and a "20x" plan is exactly four times the "5x" bucket on the same catalog.

Tiered and dimension-dynamic pricing

Tiers, provider/model catalogs, and modifiers are catalog concerns, not plan concerns: see Pricing catalogs. Use fs.rate.graduated([...]) when each band keeps its own rate and fs.rate.volume([...]) when crossing a threshold reprices the whole window at close.

Resource-priced products

Use fs.resource() limits for persistent inventory such as projects or seats. Bill seats by reporting the active count as a measure from your backend rather than summing seat-change events.

Decision checklist

  • What does the customer believe they are buying: access, use, prepaid value, or an allowance? That answer is the plan kind.
  • Can the quantity be measured authoritatively from the backend?
  • Does the customer need to stop at zero (prepaid + block) or keep going (hybrid + overage)?
  • Should subscribers see rates (transparent, the default) or only allowance (opaque)?
  • What happens at renewal, cancellation, failed payment, and plan change?
  • Can support explain the invoice from the bill preview, the release, and the ledger?

If the answer requires several exceptions, simplify the plan before reaching for custom.

PreviousPlan changesNextPricing catalogs

On this page

Free with a real boundFlat subscriptionPay as you goPrepaid walletSubscription plus included allowance and overageTrialOpaque allowances (5x / 20x)Tiered and dimension-dynamic pricingResource-priced productsDecision checklist