Kaiten

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.

TypePurposeKaiten Sushi Shop
COMMUNITYThe entry tier, offered without a commercial contractstarter — the entry tier
DEVELOPMENTInternal development, testing, and demo instancesstandard — the tier Demo Restaurant runs under
TRIALAn evaluation tier prospects run before buyingnot in the seeded profile
PAIDA production commercial contractpremium — 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

FieldRequiredConstraints
nameyes1–100 characters; the display name — the versions of a product share it, but the family, not the name, makes them one product
descriptionyesup to 500 characters
typeyesDEVELOPMENT, TRIAL, PAID, or COMMUNITY
isDefaultyesmarks the version its family puts forward — at most one per family, and only a published one
slugno2–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
familySlugnoon 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
lifecycleStatenoDRAFT or PUBLISHED on create, PUBLISHED when omitted; ARCHIVED is reached by withdrawing a version from sale
versionNamenoup 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 a POST /licenses that repeats an existing name without naming a family opens a second product.
  • The server numbers the versions. version is drawn from a counter kept on the family, starting at 1, and a number is never reused — a deleted version leaves a gap. A create that sends version is refused with 422 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=N returns that version as currentVersion, a draft or an archived one included.
  • ?include=versions adds versions, 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_UPDATED event of its own. An update that sends isDefault: false on 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.

The console's Licenses page with the Premium family open: three versions (v3 a draft, v2 published and default, v1 archived), the family's default badge, and the New Version buttonThe console's Licenses page with the Premium family open: three versions (v3 a draft, v2 published and default, v1 archived), the family's default badge, and the New Version button

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:

StateMeaningAssignable to an instanceServed by its family
DRAFTBeing prepared, never yet on saleYes — to try it before it is publishedNo
PUBLISHEDOn saleYesYes — as the default, or as the highest-numbered published version of a family without one
ARCHIVEDWithdrawn from saleNo — the instances already on it keep itNo

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:

CallMoveRefused with 409 Conflict
POST /licenses/{slug}/publishDRAFT → PUBLISHEDPublishLicense.NotADraft — the version is already published, or archived
POST /licenses/{slug}/archivePUBLISHED → ARCHIVEDArchiveLicense.NotPublished — the version is a draft, or already archived; ArchiveLicense.DefaultMustBePublished — it is its family's default
POST /licenses/{slug}/unarchiveARCHIVED → PUBLISHEDUnarchiveLicense.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 a PUT /instances/{slug} that changes licenseId, are refused with 422 Unprocessable Entity (CreateInstance.LicenseArchived, UpdateInstance.LicenseArchived). An instance already on the version keeps it and stays editable: Kappa Kyoto still runs premium, 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.

FieldAccepted on updateRefused with 422 Unprocessable Entity
slugThe slug in the path, or omitted — a License's slug never changesUpdateLicense.SlugNotRenameable
versionThe stored number, or omittedUpdateLicense.VersionNotSettable
lifecycleStateThe stored state, or omitted — it moves through publish, archive, and unarchiveUpdateLicense.LifecycleStateNotSettable
familyIdThe License's own family, or omittedUpdateLicense.FamilyNotReassignable
familySlugNever — a version does not move to another familyUpdateLicense.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 nameEvent typeWhen
LICENSE_CREATEDcom.kaiten.license.v1.createdA License is created — the first version of a family, or a new version of one
LICENSE_UPDATEDcom.kaiten.license.v1.updatedA License's details are replaced; also emitted for the version that loses its family's default to another
LICENSE_PUBLISHEDcom.kaiten.license.v1.publishedA draft version is put on sale
LICENSE_ARCHIVEDcom.kaiten.license.v1.archivedA published version is withdrawn from sale
LICENSE_UNARCHIVEDcom.kaiten.license.v1.unarchivedAn archived version is put back on sale
LICENSE_DELETEDcom.kaiten.license.v1.deletedA License is deleted
LICENSE_FAMILY_CREATEDcom.kaiten.license_family.v1.createdA License created without naming a family opens one
LICENSE_FAMILY_UPDATEDcom.kaiten.license_family.v1.updatedA 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_DELETEDcom.kaiten.license_family.v1.deletedA 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.

On this page