Subscriptions & usage
How a subscription pins its plan, how measurements become charges, and how funding and settlement close the loop.
A subscription assigns one subscriber organization to one compiled plan inside one commercial release. That assignment drives gateway grants and limits, selects the rating context that prices the subscriber's measurements, and issues the plan's funding buckets.
Define the contract
import * as fs from "@farthershore/business";
const requests = fs.requests();
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))],
});
const api = fs.backend("api", {
transport: { mode: "direct" },
default: true,
});
const account = fs.route("/v1/account", {
get: { backend: api, costs: [requests.fixed(1)], reports: [usage] },
});
fs.meterRoutes("account-usage", account, { reports: [usage] });
fs.plan("pro", {
kind: fs.plan.kind.hybrid,
price: fs.money.usd(29).monthly(),
usagePricing: usagePricing.current(),
funding: { buckets: [fs.included(fs.money.usd(5))] },
spendPolicy: {
onExhaustion: fs.exhaustion.overage(usagePricing.current()),
},
grants: [account],
limits: [requests.perMinute(120)],
});
export default fs.business();
The grant authorizes the route. The meterRoutes binding says the backend
reports api_usage there. The catalog prices each unit. The included bucket
pays for the first $5 of rated usage each period; overage is rated at the same
catalog and settled on the invoice. The recurring fee charges independently of
usage.
The pipeline
Measurement → Rating context → RatedCharge → Funding → Ledger → Settlement
- Measurement. The backend reports facts (
values,dims) throughreport(). The gateway stamps the served identity — subscription, release, rating context — inside the signed usage event. - Rating context. Core resolves the plan's
usagePricingbinding to an immutable rating context: catalog version, selector match sets, contract modifiers, tier semantics, exact rational rates. - RatedCharge. The rating engine computes an exact nanodollar charge per measurement. Rating is order-insensitive; a deterministic re-rating pass at window close handles cumulative tiers and late events.
- Funding. Eligible buckets pay the charge in a fixed total order
(
priority, soonest expiry,promo < referral < included < prepaid, bucket id). The remainder becomes amount due. - Ledger. Every step is a balanced posting; bucket balances are a view of the ledger and are reconciled against it.
- Settlement. Stripe collects amount due and recurring fees. Stripe never rates usage or owns a balance.
Read Ledger & settlement for the operator view.
What a subscription pins
A subscription holds two pins:
- the recurring-price pin — the fee promised when it was created; a later release never changes it silently;
- the usage-pricing binding — normally
pricing.current(), so metered rates track the live catalog automatically.withContractTerms()also tracks activated catalog releases while preserving the subject's agreement terms; those terms change only through an agreement amendment.fixedVersion(n)never moves.
Publishing a release with a new recurring price affects new subscriptions. Moving an existing subscriber to the latest plan is a deferred post-launch operation.
Subscription lifecycle
Checkout creates settlement intent, but access changes only when the platform records the authoritative lifecycle transition. Payment events can arrive later, repeat, or be delivered out of order; the control plane deduplicates them by event id and applies them to durable subscription state.
Render pending, active, past-due, trial, scheduled-transition, and canceled
states rather than assuming checkout is synchronous. A trial (lifecycle: { trialDays }) suppresses obligation while it runs; rating still records the
served context.
What the subscriber sees
The only money surface for subscribers is the
bill preview API, which runs the same rating
engine and ledger reads as invoicing — preview equals invoice by construction.
Plans with fs.disclosure.opaque return allowance-remaining only; no rate can
be recovered from the response.
Limits are not invoices
The edge may deny use because of rate, capacity, resource, or funding state
(credit_exhausted on a block plan). An invoice can still include already
accepted usage. Conversely, successful payment does not bypass a route or
member permission deny. Handle the stable
deny envelope in the client rather than inferring
state from Stripe.
Cancellation and renewal
Period-end cancellation keeps the current compiled entitlements until the effective boundary. Included buckets are reissued each period by the plan's posting template; unused included value expires as a contra reversal, unused prepaid value is the subscriber's until it expires or is refunded per the bucket's rules. Delayed settlement events resolve against the release the usage was admitted under.
Use subscription state and transition timestamps from Farther Shore for UI; Stripe ids are implementation details.