Skip to content
Paykit
Esc
↑↓navigate↵open⌘Jpreview
On this page

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.

Was this page helpful?