Features
What a plan meters or grants, and how a feature states its kind and its credit rate.
A feature is the thing a plan gives an account. It is also the unit every read and write names, so the catalog states it once and everything else refers to it.
import { Paykit } from "@potion.run/paykit";
export const messages = Paykit.feature({ id: "messages", name: "Messages" });
id is what a command sends, so treat it as a public name. name is the label
a pricing page or the console shows.
Pick the kind
The kind decides which verbs accept the feature, and the compiler carries it into a typed client:
| Kind | What the account holds | Used by |
|---|---|---|
metered (default) |
Units that are spent down | A plan item, TrackUsage, Hold |
non_consumable |
A quantity that is bought and occupied | SetQuantity and SetUsage |
flag |
An on/off capability with no count | grant(), answered by check() |
export const seats = Paykit.feature({
id: "seats",
name: "Seats",
kind: "non_consumable",
});
export const sso = Paykit.feature({ id: "sso", name: "SSO", kind: "flag" });
A flag has nothing to spend down. It cannot carry an allowance, credit rates or a
rate card, and track refuses it. Grant it from a plan with Paykit.grant(sso).
A quantity is bought, not spent. A seat has a purchased count and an occupied
count, and the two are different facts. Set them with SetQuantity and
SetUsage.
See Plans and prices for how a plan grants each kind.
Price a unit in credits
A feature can be priced in credits instead of a plain count. The simple form names a rate per dimension:
export const ai = Paykit.feature({
id: "ai",
name: "AI",
creditRates: { default: 1, "gpt-large": 10 },
});
default prices an event with no dimension. A named dimension prices an event
whose dimension matches. A credit-priced feature draws on the plan’s credit
pool, so it cannot also carry an allowance.
A rate card covers the rest: property rules and multipliers.
export const render = Paykit.feature({
id: "render",
name: "Render",
rateCard: Paykit.rateCard({
default: 1,
rules: [{ match: { size: "large" }, credits: 16 }],
multipliers: [{ match: { lifecycle: "spot" }, factor: 0.3 }],
}),
});
The first matching rule wins. Every matching multiplier applies, so the final
rate is rate × all factors + all adds, clamped at zero. A rule with an empty
match is the fallback after default.
See Credits for the pool, expiry and one-off packs.
What the deploy checks
Paykit.BillingStack validates the catalog before it provisions anything, and
it fails with a typed error that names the feature. A flag that carries credit
rates or a rate card fails. A credit-priced feature that also carries an
allowance fails. A tier that grants a feature the catalog never declared fails.
A rate that is not a positive integer fails to decode.