Monetization
License Types
The four License types, the fields a License carries, how versions form a License family and move through their lifecycle, and what a family's default version marks — verified against the API.
A License is what a customer bought — one version of a product, granting entitlements, with instances attached to it. This page covers the License record itself: its type, its fields, the family its versions form, their lifecycle, and the default version. What a License grants is the next page, Pricing Tiers.
Examples use $KAITEN_API and $KAITEN_TOKEN as set up in the quickstart, and add new Licenses next to the seeded Kaiten Sushi Shop ones rather than altering them. Responses are illustrative — ids and timestamps depend on your stack.
The four types
Every License has a type, one of four values. The type classifies the commercial purpose of the License; Kaiten stores it, returns it, and exposes it to feature-flag targeting rules as __kaiten.license.type. Enforcement and expiry live elsewhere — enforcement on each grant, as its value and overage allowance (limitCapExceededOveragePercent), the license period on the instance's startLicenseDate and endLicenseDate.
| Type | Purpose | Kaiten Sushi Shop |
|---|---|---|
COMMUNITY | The entry tier, offered without a commercial contract | starter — the entry tier |
DEVELOPMENT | Internal development, testing, and demo instances | standard — the tier Demo Restaurant runs under |
TRIAL | An evaluation tier prospects run before buying | not in the seeded profile |
PAID | A production commercial contract | premium — Sakura Tokyo's and Kappa Kyoto's tier |
A rule such as __kaiten.license.type == 'PAID' targets every paying customer without listing one; promo-banner in the seeded profile does the same by product with __kaiten.license.familySlug in ['standard', 'premium'] — the family slug, shared by every version, where __kaiten.license.slug names one version. See feature flags.
Fields
| Field | Required | Constraints |
|---|---|---|
name | yes | 1–100 characters; the display name — the versions of a product share it, but the family, not the name, makes them one product |
description | yes | up to 500 characters |
type | yes | DEVELOPMENT, TRIAL, PAID, or COMMUNITY |
isDefault | yes | marks the version its family puts forward — at most one per family, and only a published one |
slug | no | 2–100 characters, ^[a-z0-9][a-z0-9-]*[a-z0-9]$, unique per organization; when omitted, a new version of a family takes {familySlug}-v{version}, and any other License a slug derived from its name with a random suffix; the first version gives its family its slug |
familySlug | no | on create only, and never returned: the family this License is a new version of (familyId names it by identifier); omit both to open a new family, of which this License is version 1 |
lifecycleState | no | DRAFT or PUBLISHED on create, PUBLISHED when omitted; ARCHIVED is reached by withdrawing a version from sale |
versionName | no | up to 50 characters; a human-readable label for the version, defaulting to Version - <version> |
The response adds id, familyId, version (assigned by the server, never reused within the family), lifecycleState, createdAt, and updatedAt. Reading requires read:licenses; every write requires write:licenses.
Create a License
Add a TRIAL tier to Kaiten Sushi Shop — Tasting Menu, the tier a prospect evaluates before choosing Starter or Premium:
curl -X POST "$KAITEN_API/licenses" \
-H "Authorization: Bearer $KAITEN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Tasting Menu",
"slug": "tasting-menu",
"description": "Evaluation tier for prospects — the Starter limits, time-boxed by the instance license period.",
"type": "TRIAL",
"isDefault": false
}'{
"id": "e4d5c6b7-a8f9-4a0b-9c1d-2e3f4a5b6c7d",
"name": "Tasting Menu",
"slug": "tasting-menu",
"description": "Evaluation tier for prospects — the Starter limits, time-boxed by the instance license period.",
"type": "TRIAL",
"version": "1",
"versionName": "Version - 1",
"isDefault": false,
"familyId": "a9b8c7d6-e5f4-4a3b-9c2d-1e0f9a8b7c6d",
"lifecycleState": "PUBLISHED",
"createdAt": "2026-09-08T10:00:00Z",
"updatedAt": "2026-09-08T10:00:00Z"
}HTTP/1.1 201 Created. With no familySlug, the License opens a family of its own, tasting-menu, as its version 1 — published, so it can be assigned. LICENSE_FAMILY_CREATED and LICENSE_CREATED are emitted. The License grants nothing yet — Pricing Tiers attaches the entitlements.
License families and versions
A License is one version of a product, and the product is a License family. The first License of a product opens its family and gives it its slug; each later version is a new License created in that family — a distinct record with its own slug, its own grants, and its own lifecycle state. Kaiten Sushi Shop sells three families: starter, with one version; standard, with two; and premium, with three — premium, premium-v2, and premium-v3.
Three rules hold a family together:
- The family makes the product; the name is a label. Two families may share a
name, and aPOST /licensesthat repeats an existing name without naming a family opens a second product. - The server numbers the versions.
versionis drawn from a counter kept on the family, starting at1, and a number is never reused — a deleted version leaves a gap. A create that sendsversionis refused with422 Unprocessable Entity(CreateLicense.VersionNotSettable). - A version's slug is derived unless you send one. The first version and its family share a slug; a later version takes
{familySlug}-v{version}—premium-v2.
Where the number of Licenses in an organization's catalog is capped — the Licenses in catalog row of the editions matrix — the cap counts families: opening a family counts, adding a version never does, and a family beyond the cap is refused with 409 Conflict (CreateLicense.EntitlementLimitReached).
What a family serves
A family serves one version — its default if it has one, otherwise its highest-numbered published version. GET /license-families/{familySlug} returns it as currentVersion: the stable address of the current version of this product, so a caller that assigns a License never has to name a version slug.
curl "$KAITEN_API/license-families/premium" \
-H "Authorization: Bearer $KAITEN_TOKEN"{
"id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
"slug": "premium",
"currentVersion": {
"id": "b2f3e4d5-c6a7-4b8c-9d0e-1f2a3b4c5d6e",
"name": "Premium",
"slug": "premium-v2",
"description": "Multi-location restaurant groups with larger delivery fleets and fleet analytics.",
"type": "PAID",
"version": "2",
"versionName": "Premium v2",
"isDefault": true,
"familyId": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
"lifecycleState": "PUBLISHED",
"createdAt": "2026-09-08T10:00:00Z",
"updatedAt": "2026-09-08T10:00:00Z"
},
"versionCount": 3,
"createdAt": "2026-09-08T10:00:00Z",
"updatedAt": "2026-09-08T10:00:00Z"
}Premium resolves to premium-v2, its default: premium-v3 is newer but a draft, and premium, the first version, is archived. GET /licenses/{slug} keeps addressing one version by its own slug, whatever its state — GET /licenses/premium reads the archived first version. Two parameters widen the family read:
?version=Nreturns that version ascurrentVersion, a draft or an archived one included.?include=versionsaddsversions, every version of the family, oldest first.
A family with nothing published has nothing to serve: the read answers 404 Not Found (GetLicenseFamily.NoPublishedVersion) unless include=versions is set, and GET /license-families still lists the family, without currentVersion.
The default version
isDefault marks the version a family puts forward, and it is scoped to the family: each family has its own default, or none, and the organization has no default License across them.
- At most one version per family holds it, and only a published one. Asking for it on a draft or an archived version is refused with
409 Conflict(CreateLicense.DefaultMustBePublished,UpdateLicense.DefaultMustBePublished). - Setting it moves it. The family's previous default loses the flag in the same write and gets a
LICENSE_UPDATEDevent of its own. An update that sendsisDefault: falseon the default leaves the family without one. - A default outranks a newer version. The family serves its default even when a higher-numbered version is published; a family without a default serves its highest-numbered published version.
- The default cannot be archived until another version takes its place or the flag is unset —
409 Conflict(ArchiveLicense.DefaultMustBePublished).
Kaiten Sushi Shop shows the three cases. Standard keeps serving v1, its default, although v2, which adds courier tracking, is on sale; Premium's default is v2; Starter's only version is its own default. POST /instances always requires an explicit licenseId, so no instance is attached to a default by omission.
Add a version
In the console, Licenses lists one entry per family, with its version count, a badge naming its default version, and the table of its versions. New Version on a family opens a form on the version the family serves: it keeps that version's name and type, proposes the next version name, and copies its grants into the editor for you to change. Saving creates the version as a draft, attaches its grants, then publishes it; check Save as draft to stop before the last step.
Over the API, a version is a POST /licenses that names its family with familySlug — or with familyId, the same family by identifier; sent together, the two must agree. A family that does not exist answers 404 Not Found (CreateLicense.FamilyNotFound). Prepare next season's terms as a fourth version of Premium, created as a draft so that nothing serves it yet:
curl -X POST "$KAITEN_API/licenses" \
-H "Authorization: Bearer $KAITEN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Premium",
"familySlug": "premium",
"description": "Multi-location restaurant groups — 2027 terms.",
"type": "PAID",
"isDefault": false,
"lifecycleState": "DRAFT",
"versionName": "2027 terms"
}'{
"id": "f5e6d7c8-b9a0-4b1c-8d2e-3f4a5b6c7d8e",
"name": "Premium",
"slug": "premium-v4",
"description": "Multi-location restaurant groups — 2027 terms.",
"type": "PAID",
"version": "4",
"versionName": "2027 terms",
"isDefault": false,
"familyId": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
"lifecycleState": "DRAFT",
"createdAt": "2026-09-08T10:05:00Z",
"updatedAt": "2026-09-08T10:05:00Z"
}HTTP/1.1 201 Created, and LICENSE_CREATED is emitted. No slug was sent, so the version took premium-v4 — 4 because the family's counter stood at three. Nothing that Sakura Tokyo or Kappa Kyoto runs under changes: instances stay on the version they were sold, and the family keeps serving premium-v2.
Over the API, a new version starts with no grants — the console's New Version copies them, the API does not. Copy the ones that do not change — GET /licenses/premium-v2/entitlements, then one POST /licenses/premium-v4/entitlements per row, carrying both the value and the limitCapExceededOveragePercent of each numeric grant — while the version is still a draft, then publish it.
Lifecycle
Every version is in one of three states, returned as lifecycleState:
| State | Meaning | Assignable to an instance | Served by its family |
|---|---|---|---|
DRAFT | Being prepared, never yet on sale | Yes — to try it before it is published | No |
PUBLISHED | On sale | Yes | Yes — as the default, or as the highest-numbered published version of a family without one |
ARCHIVED | Withdrawn from sale | No — the instances already on it keep it | No |
A License is created PUBLISHED, or DRAFT when the request says so. ARCHIVED is refused on create with 422 Unprocessable Entity (CreateLicense.LifecycleStateNotSettable): a version is archived by withdrawing it from sale. After creation, the state moves only through three calls:
| Call | Move | Refused with 409 Conflict |
|---|---|---|
POST /licenses/{slug}/publish | DRAFT → PUBLISHED | PublishLicense.NotADraft — the version is already published, or archived |
POST /licenses/{slug}/archive | PUBLISHED → ARCHIVED | ArchiveLicense.NotPublished — the version is a draft, or already archived; ArchiveLicense.DefaultMustBePublished — it is its family's default |
POST /licenses/{slug}/unarchive | ARCHIVED → PUBLISHED | UnarchiveLicense.NotArchived — the version is a draft, or published |
Each call takes no body, requires write:licenses, and returns 200 OK with the License in its new state. No call leads back to DRAFT. Publish the fourth version of Premium:
curl -X POST "$KAITEN_API/licenses/premium-v4/publish" \
-H "Authorization: Bearer $KAITEN_TOKEN"{
"id": "f5e6d7c8-b9a0-4b1c-8d2e-3f4a5b6c7d8e",
"name": "Premium",
"slug": "premium-v4",
"description": "Multi-location restaurant groups — 2027 terms.",
"type": "PAID",
"version": "4",
"versionName": "2027 terms",
"isDefault": false,
"familyId": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f",
"lifecycleState": "PUBLISHED",
"createdAt": "2026-09-08T10:05:00Z",
"updatedAt": "2026-09-08T10:20:00Z"
}premium-v4 is now on sale and can be assigned, and LICENSE_PUBLISHED is emitted. The Premium family still serves premium-v2: publishing changes what a family serves only where the family has no default.
In the console, each row of a family's versions table offers the one move its state allows — Publish, Archive, or Unarchive — behind a confirmation. Archive is disabled on the family's default, Set as default is offered on published versions, and only a draft offers Delete.
What each state means for the records around the version:
- An archived version cannot be assigned.
POST /instances, and aPUT /instances/{slug}that changeslicenseId, are refused with422 Unprocessable Entity(CreateInstance.LicenseArchived,UpdateInstance.LicenseArchived). An instance already on the version keeps it and stays editable: Kappa Kyoto still runspremium, archived after it was bought. - A draft can be assigned, to try a version on an instance before it goes on sale.
- Grants are written in every state. A draft is completed before it is published; a grant changed on a published or an archived version changes what the instances on it are granted, from their next read or report.
Update a License
PUT /licenses/{slug} replaces what a version says about itself: name, description, type, versionName, and isDefault. The body has the schema of the read, so a License can be sent back as it was read: the fields that identify the version are accepted as echoes of their stored values, and refused otherwise.
| Field | Accepted on update | Refused with 422 Unprocessable Entity |
|---|---|---|
slug | The slug in the path, or omitted — a License's slug never changes | UpdateLicense.SlugNotRenameable |
version | The stored number, or omitted | UpdateLicense.VersionNotSettable |
lifecycleState | The stored state, or omitted — it moves through publish, archive, and unarchive | UpdateLicense.LifecycleStateNotSettable |
familyId | The License's own family, or omitted | UpdateLicense.FamilyNotReassignable |
familySlug | Never — a version does not move to another family | UpdateLicense.FamilyNotReassignable |
The five replaced fields are written as sent, so a versionName left out is cleared. Read the License, change what you mean to change, and send it back:
curl -i -X PUT "$KAITEN_API/licenses/tasting-menu" \
-H "Authorization: Bearer $KAITEN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Tasting Menu",
"description": "Evaluation tier for prospects — the Starter limits for a 30-day instance license period.",
"type": "TRIAL",
"version": "1",
"versionName": "Version - 1",
"lifecycleState": "PUBLISHED",
"isDefault": false
}'HTTP/1.1 204 No Content, and LICENSE_UPDATED is emitted. The update touches the License record only: its grants are separate rows with their own endpoints, and the instances attached to it keep their licenseId. The console edits none of these fields on an existing version — a version's page changes its grants, its state, and its default flag.
Delete a License
curl -i -X DELETE "$KAITEN_API/licenses/tasting-menu" \
-H "Authorization: Bearer $KAITEN_TOKEN"HTTP/1.1 204 No Content, and LICENSE_DELETED is emitted. tasting-menu was its family's only version, so the family goes with it — LICENSE_FAMILY_DELETED — and its slug is free for a new product. Deleting one version among several leaves the family in place, and the deleted number is never given to another version: delete premium-v4, and the next version of Premium is 5.
A version that an instance runs under, or that still grants an entitlement, cannot be deleted — the call returns 409 Conflict (DeleteLicense.InUseConflict). Remove its grants first (DELETE /licenses/{slug}/entitlements/{entitlementSlug}).
Archive or delete? To stop selling a version that instances run under, archive it: the version leaves sale, its instances keep their contract, and nothing has to be moved. Delete is for a draft that was never on sale — a draft cannot be archived — and for a version nothing references.
Events
| Event name | Event type | When |
|---|---|---|
LICENSE_CREATED | com.kaiten.license.v1.created | A License is created — the first version of a family, or a new version of one |
LICENSE_UPDATED | com.kaiten.license.v1.updated | A License's details are replaced; also emitted for the version that loses its family's default to another |
LICENSE_PUBLISHED | com.kaiten.license.v1.published | A draft version is put on sale |
LICENSE_ARCHIVED | com.kaiten.license.v1.archived | A published version is withdrawn from sale |
LICENSE_UNARCHIVED | com.kaiten.license.v1.unarchived | An archived version is put back on sale |
LICENSE_DELETED | com.kaiten.license.v1.deleted | A License is deleted |
LICENSE_FAMILY_CREATED | com.kaiten.license_family.v1.created | A License created without naming a family opens one |
LICENSE_FAMILY_UPDATED | com.kaiten.license_family.v1.updated | A family starts serving another version, or none — after a publish, an archive, an unarchive, a default taken or given up, or the deletion of the version it served |
LICENSE_FAMILY_DELETED | com.kaiten.license_family.v1.deleted | A family's last version is deleted |
The first six events carry the License as the API returns it, familyId and lifecycleState included. The three License family events carry the family as GET /license-families/{familySlug} returns it — its slug, its version count, and the version it serves. A family has no write operation of its own: its events are recorded in the same transaction as the version write that caused them, and a write that leaves the served version where it was — a draft added, a rename, a default restated — emits none.
Where to go next
Attach entitlements to a License and build a tier: Pricing Tiers. The one-screen version: Licenses & Entitlements, and License, not Plan for why the word is deliberate. Exact reference: the license_family and license tables.
Overview
The contract side of Kaiten — the License a customer bought, the values it grants, the Instance it is attached to, and the usage observed against it.
Pricing Tiers
Build a tier from one catalog of entitlements, one License per tier, and one grant per pair — the Kaiten Sushi Shop tiers as the worked example, every call verified against the API.