Monetization
Prices
What a License version costs — flat fees per billing period, usage-based and overage prices on a metered entitlement — their timing, periods, currency, defaults, the rules that freeze them, and the invoice preview, verified against the API.
A price hangs on a License version and is one billable concern: one line of an invoice. A version carries several, all in one currency — flat fees, one of which each subscription to the version is pinned to as its base, and metered prices, which bill the usage of an entitlement on every subscription to the version. Prices need billing to be on; they are read with read:licenses and written with write:licenses.
Examples use $KAITEN_API and $KAITEN_TOKEN as set up in the quickstart, on a stack where billing is on. The demo seed writes Kaiten Sushi Shop's prices: on a seeded stack they already exist on Standard v1, so read them with GET /licenses/standard/prices, or follow the calls below on a version of your own. Responses are illustrative — ids and timestamps depend on your stack.
Three shapes
A price's billingModel says what it bills:
billingModel | Bills | Billing period | Timing | Kaiten Sushi Shop |
|---|---|---|---|---|
FLAT_FEE | An amount per billing period, at a quantity of 1 | Its own: MONTHLY, QUARTERLY, SEMI_ANNUAL, or ANNUAL | ADVANCE by default, or ARREARS | Standard, monthly — 49.00 EUR a month |
USAGE_BASED | An amount per sale unit of a metered entitlement's usage, from the first unit | None: it bills over the period of the subscription | ARREARS | — |
OVERAGE | An amount per sale unit of the usage above the grant's value | None | ARREARS | Orders above the allowance — 0.50 EUR per order of monthly-orders |
Timing. ADVANCE bills the period that starts at a boundary, ARREARS the period that ends there. A metered price is always ARREARS — usage cannot be billed before it happens — and is created so when billingTiming is omitted; a flat fee defaults to ADVANCE. Any other combination is refused (422 CreateLicensePrice.InvalidTiming). A subscription whose base bills in advance gets its first invoice when it starts; one whose base bills in arrears gets nothing before its first period closes.
Periods. A flat fee is charged for its billingPeriod — one, three, six, or twelve months, counted from the subscription's anchor, the day clamped to the end of a shorter month. A metered price has none (billingPeriod: null): it bills over the period of the subscription's base price. A period is required on a flat fee and refused on a metered price (422 CreateLicensePrice.InvalidPeriod).
Amounts and currency
No amount is a float (API conventions). A price is created with unitAmountDecimal, a decimal string in the currency's minor units — "4900" is 49.00 EUR, "50" is 0.50 EUR — of at most 12 integer digits and 12 decimal places, so a fraction of a cent per unit can be priced (422 CreateLicensePrice.InvalidAmount otherwise). The response repeats it as unitAmount, an integer, when it is a whole number of minor units, and null otherwise. On an invoice, a line's amount is its quantity times the unit amount, rounded half up once.
currency is an ISO 4217 code in upper case (422 CreateLicensePrice.InvalidCurrency). A version bills in one currency, fixed by its first price: a price in another one is refused (422 CreateLicensePrice.CurrencyMismatch), deprecated prices included. Every price of a FREE version is 0 (422 CreateLicensePrice.FreePriceNotZero) — see the pricing type.
Create a price
POST /licenses/{slug}/prices adds a price to a version. Standard v1 sells two flat fees, each the default of its period:
curl -X POST "$KAITEN_API/licenses/standard/prices" \
-H "Authorization: Bearer $KAITEN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"billingModel": "FLAT_FEE",
"billingPeriod": "MONTHLY",
"currency": "EUR",
"unitAmountDecimal": "4900",
"displayLabel": "Standard, monthly",
"displayOrder": 1,
"isDefault": true
}'Standard, yearly is the same call with ANNUAL, "49000", and a displayOrder of 2. Then the orders above Standard's allowance:
curl -X POST "$KAITEN_API/licenses/standard/prices" \
-H "Authorization: Bearer $KAITEN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"billingModel": "OVERAGE",
"meteredEntitlementSlug": "monthly-orders",
"currency": "EUR",
"unitAmountDecimal": "50",
"displayLabel": "Orders above the allowance",
"displayOrder": 3
}'{
"id": "8c2d4e6f-1a3b-4c5d-9e7f-0a1b2c3d4e5f",
"billingModel": "OVERAGE",
"billingTiming": "ARREARS",
"billingPeriod": null,
"currency": "EUR",
"unitAmount": 50,
"unitAmountDecimal": "50",
"metered": {
"entitlementSlug": "monthly-orders",
"saleUnitFactor": "1",
"saleUnitSingular": "order",
"saleUnitPlural": "orders"
},
"displayLabel": "Orders above the allowance",
"displayOrder": 3,
"isDefault": false,
"status": "ACTIVE",
"deprecatedAt": null,
"createdAt": "2026-09-08T10:01:00Z",
"updatedAt": "2026-09-08T10:01:00Z"
}HTTP/1.1 201 Created, and LICENSE_PRICE_CREATED is emitted. The timing was omitted, so the metered price bills in ARREARS; it has no period of its own, and metered says what it measures. monthly-orders has no sale unit, so the price bills per order: a factor of 1.
In the console, the Prices tab of a version lists its prices, and Add price opens the New price drawer: the shape, the timing, the period, the currency, the label, the amount — typed in the currency's own unit and sent in minor units — and the default flag.
What a metered price measures
A USAGE_BASED or OVERAGE price names the entitlement it measures, meteredEntitlementSlug, and bills its usage over the period that ended, read from the usage journal. Only a flow can be metered — a count that starts over every window:
- a
NUMBERorNUMBER_AI_CREDITentitlement (422 CreateLicensePrice.UnsupportedEntitlementTypeotherwise); - with a reset period (
.EntitlementIsStockotherwise — a count that never resets, such asdelivery-drivers, is a stock, sold as an add-on held in a quantity); - aggregated with
SUMorCOUNT(.UnsupportedAggregationotherwise); - granted by the version (
.EntitlementNotGranted; an entitlement that does not exist answers404 Not Found,.EntitlementNotFound); - and metered by no other active price of the version (
.EntitlementAlreadyMetered): one metered price per entitlement.
An OVERAGE price also needs a grant that usage can pass: a finite value with an overage allowance above 0. A hard cap or an unlimited grant never goes above its value, so the price is refused (422 CreateLicensePrice.OverageUnreachable). Once an active price meters an entitlement, the version keeps its grant until the price is deprecated (409 DeleteLicenseEntitlement.MeteredByPrice).
Sale units. A metered price is priced per sale unit of its entitlement. An entitlement can be counted in one unit and sold in another — counted in tokens, sold per 10,000 tokens — through its sale unit (saleUnitSingular, saleUnitPlural, saleUnitFactor, on the entitlement); the line's quantity is then the usage divided by the factor. The factor is captured on the price when it is created, as metered.saleUnitFactor, and a later change to the entitlement never reprices it. An entitlement without a sale unit is billed in its base unit, with a factor of 1, and its prices carry no unit label. monthly-orders names the order as its sale unit, with a factor of 1, so its overage price reads per order.
What the grant lets through. A usage report beyond what the grant accepts is refused before it is stored, so a metered price only ever bills what the grant let through. Standard grants monthly-orders at 100 with a 10 % allowance: reports are accepted up to 110 orders a month, and Orders above the allowance bills each order above 100 — at most 10 a month, 5.00 EUR. The overage is counted against the limit in force when each report was accepted, which the invoice line lists.
The default price of each period
isDefault marks a flat fee as the default of its billing period: at most one per period and version (409 CreateLicensePrice.DefaultConflict), and only an active flat fee (422 .InvalidDefault on a metered price). The default is the price taken when none is named: the public catalogue shows the default flat fee of each period, an anonymous checkout that names a version and a period buys it, and the invoice preview starts from the version's first default in display order. Standard v1 has two defaults — Standard, monthly for MONTHLY, Standard, yearly for ANNUAL. Deprecating a default takes the flag away in the same write.
Label and order
displayLabel, up to 200 characters, is the label of the invoice line the price produces. Left out, the line derives one: the License name and — base for a flat fee (Standard — base), the entitlement's name for a usage-based price, and the entitlement's name and — overage for an overage price (Monthly Orders — overage). displayOrder, 0 or more, orders the version's prices — in GET /licenses/{slug}/prices, the console, and the public catalogue.
What a version lets you change
Prices follow the lifecycle of their version, and a subscription freezes what it bills:
| Version | Add a price | Edit a price | Deprecate a price |
|---|---|---|---|
DRAFT | Yes | Yes | Yes |
PUBLISHED | Yes, until a subscription bills the version — then 409 CreateLicensePrice.BillingActive | No — 409 UpdateLicensePrice.VersionNotDraft | Yes |
ARCHIVED | No — 409 CreateLicensePrice.VersionArchived | No | Yes |
PATCH /licenses/{slug}/prices/{priceId} edits a price of a draft: its amount, timing, period, metered entitlement, label, order, or default flag — never its shape or its currency, which make another price. A published price never changes under what is billed from it: its only change is deprecation. To sell other terms, create a new version.
Deprecate, never delete. POST /licenses/{slug}/prices/{priceId}/deprecate retires a price for good. It is no longer offered — to a new subscription, a checkout, or a move to another price — and it stops being a default. A subscription pinned to a deprecated flat fee keeps being billed from it; a deprecated metered price bills no line from the next invoice on. A price that a subscription is scheduled to move to cannot be deprecated until the change is dropped (409 DeprecateLicensePrice.PlanChangeTarget), and deprecating a price twice answers 409 DeprecateLicensePrice.AlreadyDeprecated. No operation deletes a price: a version's prices go with it when the version is deleted.
Preview an invoice
POST /licenses/{slug}/invoice-preview composes the renewal invoice a subscription to the version would be billed at a boundary now, and writes nothing. It works on a draft — to check prices before they become immutable — and needs read:licenses. Every member of the body is optional:
| Member | What it adds | Refused with |
|---|---|---|
basePriceId | The flat fee billed as the base; when omitted, the version's first default flat fee in display order | PriceNotFound; NoBasePrice when the version has no default flat fee |
sampleUsage | Usage of each metered entitlement over the period that ends, as decimal strings, rated as one window against the grant — usage beyond what the grant accepts is capped, and the line says so | InvalidSampleUsage |
instanceSlug | An instance's own usage instead of a sample, from the start of its current billing period — or from one billing period back when it has no subscription | InstanceNotFound; OutsideRetention when that start is older than the usage kept |
addOns | Add-on versions and their quantities: their fees and metered prices are priced in, and their grants raise the limit a sample is rated against | AddonNotFound, InvalidAddOns |
voucherCode | A voucher's price discount, applied as a redemption would apply it — nothing is redeemed | VoucherNotFound, VoucherInvalid |
The codes are PreviewLicenseInvoice.*: 404 Not Found for what does not exist, 422 Unprocessable Entity otherwise. Rate a month of 106 orders on Standard v1:
curl -X POST "$KAITEN_API/licenses/standard/invoice-preview" \
-H "Authorization: Bearer $KAITEN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"sampleUsage": [{"entitlementSlug": "monthly-orders", "quantity": "106"}]}'{
"status": "PREVIEW",
"kind": "RENEWAL",
"asOf": "2026-09-08T10:00:00Z",
"boundaryAt": "2026-09-08T10:00:00Z",
"licenseSlug": "standard",
"currency": "EUR",
"subtotal": 5200,
"discountTotal": 0,
"total": 5200,
"lines": [
{
"seq": 1,
"type": "OVERAGE",
"label": "Orders above the allowance",
"description": "6 × 0.50 EUR; 106 used; 6 above the applied limit (100)",
"serviceFrom": "2026-08-08T10:00:00Z",
"serviceTo": "2026-09-08T10:00:00Z",
"quantity": "6",
"unitAmountDecimal": "50",
"amount": 300
},
{
"seq": 2,
"type": "BASE",
"label": "Standard, monthly",
"description": "1 × 49.00 EUR",
"serviceFrom": "2026-09-08T10:00:00Z",
"serviceTo": "2026-10-08T10:00:00Z",
"quantity": "1",
"unitAmountDecimal": "4900",
"amount": 4900
}
],
"wouldHold": []
}HTTP/1.1 200 OK, trimmed here to the members discussed: each line also carries the identifiers of its price and entitlement, and a metered line its metering and overage detail. The base is Standard, monthly, the first default in display order, billed in advance for the period that starts; the orders are rated in arrears for the period that ends — 106, of which 6 above the 100 Standard grants, at 0.50 EUR: 3.00 EUR. A sample of 125 would be capped at 110, the most the grant accepts, and its line would say so. Nothing is created: status is always PREVIEW, and wouldHold — the meters whose usage journal would hold the invoice — is always empty for a sample.
The Preview invoice button of the Prices tab runs the same call, from samples or from an instance's usage, with add-ons and a promo code:
API endpoints
| Method | Endpoint | Operation | Description |
|---|---|---|---|
GET | /licenses/{slug}/prices | listLicensePrices | A version's prices, in display order; ?status= and ?billingModel= narrow the list |
GET | /licenses/{slug}/prices/{priceId} | getLicensePrice | One price, deprecated or not |
POST | /licenses/{slug}/prices | createLicensePrice | Add a price to a version |
PATCH | /licenses/{slug}/prices/{priceId} | updateLicensePrice | Edit a price of a draft |
POST | /licenses/{slug}/prices/{priceId}/deprecate | deprecateLicensePrice | Retire a price, for good |
POST | /licenses/{slug}/invoice-preview | previewLicenseInvoice | Compose the invoice a subscription to the version would be billed, writing nothing |
The reads and the preview need read:licenses, the writes write:licenses, and every one of them billing to be on — 403 Billing.Disabled or 403 Billing.NotEntitled otherwise.
Events
| Event name | Event type | When |
|---|---|---|
LICENSE_PRICE_CREATED | com.kaiten.license.price.v1.created | A price is added to a version |
LICENSE_PRICE_UPDATED | com.kaiten.license.price.v1.updated | A price of a draft is edited; changedFields names what changed, and an edit that changes nothing emits none |
LICENSE_PRICE_DEPRECATED | com.kaiten.license.price.v1.deprecated | A price is deprecated |
Each carries the price as the API returns it, with the licenseSlug of its version and the familySlug of its family.
Where to go next
Put a tier's prices next to its grants: Pricing Tiers. Bill them: Subscriptions and Invoices. How a version is sold — its pricing type and its trial: License Types. Exact reference: the billing tables of the data model.
License Types
The four License types and the pricing type, the commercial terms a version carries, how versions form a License family with its settings and lifecycle, what a family's default version marks, and what a subscription freezes — verified against the API.
Pricing Tiers
Build a tier from one catalog of entitlements, one License per tier, and one grant per pair, then price it and list it — the Kaiten Sushi Shop tiers as the worked example, every call verified against the API.