Balances
What an account has of one feature, where it came from, and the controls a customer sets over it.
A balance is what an account can spend of one feature, and where each part
came from. check() returns one, and track() returns the balance after the
spend, so a caller never rebuilds it from the entitlement view.
const balances = yield* Paykit.Balances(account);
const balance = yield* balances.get({ featureId: "messages" });
balance.remaining; // 1_940
balance.usage; // 60 this period
What a balance holds
| Field | What it reports |
|---|---|
granted |
What the sources granted in total |
remaining |
What is spendable now. null means there is no count |
usage |
Units consumed this period. For a quantity, the occupied count |
unlimited |
Whether the allowance has no end |
overageAllowed |
Whether a spend past the grant is permitted |
nextResetAt |
When the allowance returns to its grant |
breakdown |
One row per source |
purchased, occupied |
The bought and in-use counts, for a quantity |
spendLimit |
The customer’s money ceiling and the headroom left under it |
Where the number came from
breakdown is the answer to “why is it that number”. Each source says what it
granted, what is left, when it resets and, for a plan item, which plan granted
it.
for (const source of balance.breakdown) {
source.kind; // "plan", "addon", "one_off", "rollover" or "grant"
source.remaining;
source.resetAt;
}
A plan item is a source with the plan’s lifetime. A purchase is a source that never expires with the plan. A rollover is its own source with its own boundary, which is how a carried amount stays readable after the period that produced it.
Quantities are bought, not spent
A non-consumable feature has a purchased count and an occupied count. A seat plan grants five seats, and the account sets how many of them are in use:
yield* balances.update({ featureId: "seats", usage: 3 });
The balance reports purchased, occupied and their difference. The difference
may be negative when more seats are occupied than bought, which is a state to
show and not a refusal to hide.
Grant capacity outside a plan
create adds a standalone grant. It is a source of its own, so it shows in
breakdown beside the plan’s grant:
yield* balances.create({
featureId: "messages",
grantedBalance: 500,
reset: { interval: "one_off" },
idempotencyKey: `grant:${requestId}`,
});
The grant is a lifetime source by default. An expiresAt gives it a deadline,
and the idempotencyKey makes a retry the same grant.
Set a spend limit and an alert
A customer can state controls over its own plan. They live on the customer, not in the catalog, so changing one needs no deploy.
const customers = yield* Paykit.Customers(account);
yield* customers.update({
billingControls: {
spendLimits: [{ featureId: "messages", amount: 5_000 }],
usageAlerts: [{ featureId: "messages", at: 1_500 }],
},
});
A spend limit is money in minor units for one period. A spend past it is refused
with SpendLimitExceeded, and the subscription stays usable. An alert is a
threshold that records UsageAlertFired and refuses nothing.
Finish a lock
finalize closes a hold opened by a locked check or by Paykit.Hold. commit
bills what ran, and release returns all of it.
yield* balances.finalize({ lockId, action: "commit", overrideValue: unitsUsed });
See Check for placing the lock, and Holds for a hold that spans several calls.