Kaiten

Developer Resources

Data Model

The exact reference — every entity, enum, and constraint in the Kaiten schema, with the full ERD.

This page is the single exact reference for Kaiten's data model: the entities, their columns, the enums, and the constraints that hold the model together. Concept pages carry the mental model; when they say "exact reference", they mean here. It reflects the schema as of 2026-10-12 — 30 tables and 10 enums.

Examples use the Kaiten Sushi Shop dataset — the organization the seeder's demo profile loads on the Community Edition and the demo sandbox seeds on Kaiten Cloud: the Customers Sakura Tokyo, Ninja Osaka, Demo Restaurant, and Kappa Kyoto, six instances on versions of the Starter, Standard, and Premium Licenses, and entitlements like monthly-orders and menu-items. Seed data describes the dataset.

The ERD

Every domain table carries an organization_id — the organization is the isolation boundary, and deleting one cascades its entire subtree — with two exceptions. connector is the global registry of connector builds; what an organization activated is its own row in organization_connector. The three tables behind in-app notifications — notification_read, notification_user_state, and notification_preference — belong to a user and carry no organization.

Enums

EnumValues
entitlement_typeBOOLEAN · NUMBER · CONFIG · NUMBER_AI_CREDIT
aggregation_methodSUM · COUNT · AVERAGE · MAX · MIN · LATEST
entitlement_reset_periodHOUR · DAY · WEEK · MONTH · YEAR
entitlement_reset_anchorCALENDAR · LICENSE_START
license_typeDEVELOPMENT · TRIAL · PAID · COMMUNITY
license_lifecycle_stateDRAFT · PUBLISHED · ARCHIVED
instance_statusHEALTHY · DEGRADED · INCIDENT · MAINTENANCE
metadata_field_resource_typeDEPLOYMENT_ZONE · INSTANCE
token_kindorganization · platform
user_typehuman · machine

There is no enforcement enum: how strictly a NUMBER grant is enforced is derived from two numbers on the license_entitlement row.

The hierarchy

organization

The root. You, the vendor.

ColumnTypeConstraints
iduuidPK
external_idtextNOT NULL, unique — the identity provider's organization ID
nametextNOT NULL

customer

Who you sell to — sakura-tokyo, ninja-osaka, demo-restaurant, kappa-kyoto.

ColumnTypeConstraints
iduuidPK; (id, organization_id) unique
nametextNOT NULL
slugtextNOT NULL, unique per organization
external_customer_idtextnullable — your BYO-ID for this customer (CRM record, billing ID); indexed per organization
domaintextnullable
organization_iduuidNOT NULL, FK → organization (delete: cascade)
created_by_id / updated_by_iduuidNOT NULL, FK → user (delete: restrict)
created_at / updated_attimestampNOT NULL

Deleting a customer cascades to its instances.

instance

One running copy of your product for one customer. The point of convergence: it references the customer, the license, and optionally a deployment zone.

ColumnTypeConstraints
iduuidPK
name / descriptiontextNOT NULL
slugtextNOT NULL, unique per organization
customer_iduuidNOT NULL, composite FK (customer_id, organization_id) → customer (delete: cascade)
license_iduuidNOT NULL, composite FK → license (delete: restrict) — one License version; see the trigger below
deployment_zone_iduuidnullable, composite FK → deployment_zone (delete: set null)
start_license_datetimestampNOT NULL, default now — the anchor of LICENSE_START usage windows
end_license_datetimestampNOT NULL, default now + 1 year
statusinstance_statusNOT NULL, default HEALTHY
lifecycle_stagevarcharnullable
metadatajsonbNOT NULL, default {} — validated against the organization's metadata fields
organization_iduuidNOT NULL, FK → organization (delete: cascade)
created_by_id / updated_by_id / timestampsas on customer

The FKs to customer, license, and zone are composite with organization_id — referencing another organization's record behaves like a record that doesn't exist. A license attached to any instance cannot be deleted. metadata is the only free-form column: every other property of an instance is a typed column.

A trigger function, instance_license_not_archived, runs when license_id is set — on insert, and on an update that changes it — and refuses a license whose lifecycle_state is ARCHIVED. It looks at nothing else: an instance already pinned to a version that is archived afterwards keeps its row and stays editable, and a DRAFT version is accepted.

The contract

license_family

The product a license is a version of — starter, standard, premium. A family has a slug and no name: the display name belongs to each version.

ColumnTypeConstraints
iduuidPK; (id, organization_id) unique
slugtextNOT NULL, unique per organization
last_versionintNOT NULL, default 0 — the version counter: the highest number any version of the family has taken, deleted versions included
organization_iduuidNOT NULL, FK → organization (delete: cascade)
created_at / updated_attimestampNOT NULL — updated_at moves when a version is added

No operation writes a family directly: the first license of a product creates the row, each version added advances last_version, and deleting the family's last license deletes the row, which frees its slug.

license

What a customer bought, one version per row — starter; standard and standard-v2; premium, premium-v2, and premium-v3.

ColumnTypeConstraints
iduuidPK; (id, organization_id) unique
family_iduuidNOT NULL, composite FK (family_id, organization_id) → license_family (delete: cascade)
nametextNOT NULL — a display label, with no uniqueness: two families may share a name
slugtextNOT NULL, unique per organization
descriptiontextNOT NULL
typelicense_typeNOT NULL
versionintNOT NULL; (family_id, version) unique — assigned on insert from the family's counter
version_nametextnullable — set to Version - <version> on insert when none is given
lifecycle_statelicense_lifecycle_stateNOT NULL, no column default — the API writes PUBLISHED unless a create asks for DRAFT
is_defaultboolNOT NULL, default false — bound by the constraints below
featuresjsonbnullable
organization_iduuidNOT NULL, FK → organization (delete: cascade)
created_at / updated_attimestampNOT NULL

Three rules hold the versions of a family together:

ConstraintRule
license_family_id_version_key(family_id, version) is unique. The update_license_version trigger numbers each row on insert as the family's last_version + 1 and advances the counter in the same statement, so a number is never reused — a deleted version leaves a gap
license_family_id_is_default_keya partial unique index on family_id where is_default — at most one default version per family
license_default_must_be_published_checkis_default only on a row whose lifecycle_state is PUBLISHED — which is why archiving a family's default is refused

lifecycle_state changes through the publish, archive, and unarchive operations only; License Types covers the transitions.

entitlement

A typed right in the catalog — monthly-orders (NUMBER), delivery-tracking (BOOLEAN), support-tier (CONFIG). The catalog carries the measurement: how a quota is counted, when it warns, when it resets. It carries nothing about enforcement.

ColumnTypeConstraints
iduuidPK
nametextNOT NULL
slugtextNOT NULL, unique per organization
descriptiontextnullable
typeentitlement_typeNOT NULL
aggregation_methodaggregation_methodrequired for NUMBER / NUMBER_AI_CREDIT, must be NULL for BOOLEAN / CONFIG (entitlement_number_aggregation_check)
warning_threshold_percentsmallintNOT NULL, 0–100, default 0 — 0 disables the warning event
reset_periodentitlement_reset_periodnullable — NULL means a lifetime counter
reset_anchorentitlement_reset_anchornullable — set exactly when reset_period is set (entitlement_reset_anchor_required_check)
unit_singular / unit_pluraltextNUMBER family only; both set or both NULL
sale_unit_singular / sale_unit_plural / sale_unit_factortext / float8all three set or none; require the base units; sale_unit_factor > 0
user_facingboolNOT NULL, default false
display_orderintNOT NULL, ≥ 0, default 0
iconvarcharnullable
organization_iduuidNOT NULL, FK → organization (delete: cascade)
created_at / updated_attimestampNOT NULL

Two more CHECKs bound the periodic windows: reset_period is allowed only on the NUMBER family (entitlement_reset_period_number_family_check) and never with LATEST aggregation (entitlement_reset_period_latest_check). An entitlement a license still grants cannot be deleted (the grant's FK is restrict); the usage recorded for it is deleted with it (its FK cascades).

license_entitlement

The contract side: the value a license grants for an entitlement, and how strictly that value is enforced — premium-v2 grants delivery-tracking = true and 1,000 monthly-orders with a 20 % allowance; starter grants false and 20 as a hard cap.

ColumnTypeConstraints
iduuidPK
license_iduuidNOT NULL, FK → license (delete: restrict); (license_id, entitlement_id) unique — one grant per pair
entitlement_iduuidNOT NULL, FK → entitlement (delete: restrict)
valuejsonbNOT NULL — an object { "type", "value" } where type is number, boolean, or object and value matches it (license_entitlement_value_check)
limit_cap_exceeded_overage_percentsmallintnullable — the enforcement of a numeric grant; see the two CHECKs below
organization_iduuidNOT NULL, FK → organization (delete: cascade)
created_by_id / updated_by_id / timestampsas on customer

The two CHECKs make enforcement fully derivable from the row, with no separate flag:

ConstraintRule
license_entitlement_overage_percent_number_only_checklimit_cap_exceeded_overage_percent is NOT NULL exactly when value.type = 'number', and NULL otherwise
license_entitlement_overage_percent_unlimited_checkwhen value.value = -1 (the unlimited sentinel) the percent is -1; when value.value ≠ -1 the percent is ≥ 0

Read together: -1 is unlimited (never refused), 0 is a hard cap (refused above the value), and a positive percent is a soft limit refused above value + value × percent / 100. The API names the column limitCapExceededOveragePercent and defaults it to -1 for an unlimited value and 0 otherwise when a grant omits it.

entitlement_usage

The observed side: current consumption of one entitlement on one instance.

ColumnTypeConstraints
entitlement_id + instance_iduuidcomposite PK; FK → entitlement (cascade), FK → instance (cascade)
valuejsonbNOT NULL — { "type": "number", "value": <number>, "event_count": <integer ≥ 0> } (entitlement_usage_value_check)
period_starttimestampnullable — the start of the window the total belongs to; NULL for a lifetime counter
organization_iduuidNOT NULL, FK → organization (delete: cascade)

There is no license_id: the license is reached through the instance. There is no history table either — the row holds the current window only, and closed windows exist as INSTANCE_ENTITLEMENT_USAGE_PERIOD_ROLLED_OVER events in the audit trail.

entitlement_group / entitlement_group_membership

A named set of entitlements with an aggregated usage view.

ColumnTypeConstraints
entitlement_group.iduuidPK; slug unique per organization; name NOT NULL, description nullable
entitlement_group_membershipcomposite PK (entitlement_group_id, entitlement_id); both FKs cascade; indexed by entitlement_id

Feature delivery

feature_flags

ColumnTypeConstraints
iduuidPK
slugvarcharNOT NULL, unique per organization — delivery-tracking-enabled
namevarcharNOT NULL
descriptiontextnullable
typevarcharNOT NULL
enabledboolNOT NULL
variantsjsonbNOT NULL — the values the flag can resolve to
default_variantjsonbNOT NULL — used when no targeting rule matches
targeting_rulesjsonbnullable — ordered CEL rules; first match wins
event_namevarcharNOT NULL
metadatajsonbnullable
organization_iduuidNOT NULL, FK → organization (delete: cascade)

Releases and deployments

component

A versioned building block — unique per organization on (name, version).

ColumnTypeConstraints
iduuidPK
name / versionvarcharNOT NULL; (organization_id, name, version) unique
slugtextNOT NULL, unique per organization
descriptiontextnullable
previous_component_iduuidnullable — links the prior version
organization_id / created_by_id / created_atNOT NULL

release

An immutable bundle of components — unique per organization on version. There is no updated_at: a release is never edited.

ColumnTypeConstraints
iduuidPK
versionvarcharNOT NULL, unique per organization
slugtextNOT NULL, unique per organization
descriptiontextnullable
previous_release_iduuidnullable, FK → release (delete: restrict)
organization_id / created_by_id / created_atNOT NULL

component_release joins the two: composite PK (component_id, release_id), both FKs restrict. A component a release bundles cannot be deleted. A release is held by two other FKs, both restrict: a deployment row that names it, and another release's previous_release_id — so a release the journal names, or that has a successor, cannot be deleted; deleting one that has neither removes its own component_release rows first.

deployment_zone

A logical execution environment, shared or dedicated.

ColumnTypeConstraints
iduuidPK; (id, organization_id) unique
namevarcharNOT NULL
slugtextNOT NULL, unique per organization
typevarcharNOT NULL — the environment class; production on all three seeded zones
descriptiontextNOT NULL
metadatajsonbnullable — validated against the organization's metadata fields; the seeded zones carry region, compliance_profile, and dedicated
organization_id / attribution / timestampsas on customer

deployment

The append-only journal: one row records one release landing in one zone. There is no update — history is the data.

ColumnTypeConstraints
iduuidPK
seqbigintNOT NULL, generated identity — the recording order of the journal
deployment_zone_iduuidNOT NULL, FK → deployment_zone (delete: cascade)
release_iduuidNOT NULL, FK → release (delete: restrict)
created_at / created_by_idNOT NULL — when and by whom
organization_iduuidNOT NULL, FK → organization (delete: cascade)

Indexed by (organization_id, deployment_zone_id, created_at, seq) and (organization_id, release_id, created_at, seq): the journal reads per zone or per release, ordered by seq — two entries recorded in the same instant still have a definite order.

Metadata

metadata_field

An admin-declared, JSON-Schema-validated property for instances or deployment zones.

ColumnTypeConstraints
iduuidPK
resource_typemetadata_field_resource_typeNOT NULL
keytextNOT NULL; (organization_id, resource_type, key) unique among fields that are not archived (uq_metadata_field_key_active)
labeltextNOT NULL
json_schemajsonbNOT NULL — JSON Schema 2020-12
display_orderintNOT NULL, default 0
archived_attimestampnullable — archived fields stop validating writes, and release their key
organization_id / attribution / timestampsas on customer

The trail and the event transport

audit_trail

Every domain event, recorded — see the audit trail for the concept.

ColumnTypeConstraints
iduuidPK
organization_iduuidNOT NULL, FK → organization (delete: cascade)
instance_iduuidnullable, FK → instance (delete: set null — entries outlive the instance)
event_name / event_typetextNOT NULL — the catalog name (ENTITLEMENT_USAGE_REPORT_REJECTED) and the dotted type
occurred_attimestamptzNOT NULL, default now; indexed
payloadjsonbnullable — the event's data

Indexed by (organization_id, instance_id) — the trail reads per instance — and by (organization_id, event_name, occurred_at, id) (idx_audit_trail_feed): one organization's entries for a set of event names, newest first, which is how the notification feed reads it.

notification_read

There is no notification table: a notification is an audit_trail row seen by a user. What is stored per user is a read state and a set of preferences, in three tables that reference the user and carry no organization_id.

notification_read marks one entry read by one user. It is sparse: a row exists only for an entry read individually.

ColumnTypeConstraints
user_iduuidNOT NULL, FK → user (delete: cascade)
audit_trail_iduuidNOT NULL, FK → audit_trail (delete: cascade)
read_attimestamptzNOT NULL, default now

Primary key (user_id, audit_trail_id).

notification_user_state

One row per user: the watermark that "mark all as read" moves. An entry is read when it has a notification_read row or occurred at or before read_all_before; moving the watermark deletes the individual marks it covers.

ColumnTypeConstraints
user_iduuidPK, FK → user (delete: cascade)
read_all_beforetimestamptzNOT NULL, default -infinity

notification_preference

A user's choice for one event on one channel. It holds overrides only: no row means the event's default, so a new notifiable event, or a changed default, reaches every user who never chose otherwise.

ColumnTypeConstraints
user_iduuidNOT NULL, FK → user (delete: cascade)
event_nametextNOT NULL — a notifiable event's catalog name
channeltextNOT NULL — in_app, the one channel served
enabledboolNOT NULL

Primary key (user_id, event_name, channel). Which events are notifiable, and their defaults, are not in the database: Notifications lists them.

outbox_events

The transactional outbox: a domain write and its events commit in the same transaction, and the change-data-capture pipeline reads them from here.

ColumnTypeConstraints
iduuidPK
organization_iduuidNOT NULL, FK → organization (delete: cascade)
event_name / event_typetextNOT NULL
occurred_attimestamptzNOT NULL, default now
datajsonbNOT NULL — the event payload
headersjsonbnullable

inbox_events

The consumer side: each in-process consumer of the event stream records what it has already processed, so a redelivery is skipped without skipping another consumer's work.

ColumnTypeConstraints
iduuidPK
organization_iduuidNOT NULL, FK → organization (delete: cascade)
sourcetextNOT NULL — which stream delivered the message
message_idtextNOT NULL
consumertextNOT NULL — which consumer processed it
processed_attimestamptzNOT NULL, default now

(organization_id, source, message_id, consumer) is unique: the dedup key is per consumer, which is what lets several consumers — the audit trail, a connector — share one delivery. Both transport tables are retention-swept on (occurred_at, id) / (processed_at, id) and are not part of the queryable model.

Identity

user

One table for humans and machines. Service accounts are machine users — the seeded ops-team and sdk among them.

ColumnTypeConstraints
iduuidPK
external_idtextNOT NULL, unique — the identity provider's subject for humans
typeuser_typeNOT NULL, default human
nametextNOT NULL
emailtextnullable, unique — machines must have none
slugtextnullable — machines must have one. Unique per organization among machine users (idx_unique_machine_slug_per_org), and among machine users without an organization (idx_unique_orgless_machine_slug)
organization_iduuidnullable, FK → organization (delete: cascade) — machines must have one; the platform's own system:kaiten identity is the single exception
deleted_attimestampnullable
created_at / created_by_idcreated_by_id nullable, FK → user (delete: set null)

Human membership in organizations goes through user_on_organization (composite PK organization_id, user_id, plus a nullable deleted_at).

token

API credentials — the ksh_ tokens of your service accounts, and the ksm_ platform credentials of a self-hosted operator.

ColumnTypeConstraints
iduuidPK
nametextNOT NULL; (service_account_id, name, organization_id) unique among non-revoked tokens
slugtextNOT NULL; (slug, organization_id) unique
hash / lookup_hashtextNOT NULL — a salted verification hash and a deterministic lookup hash; the plaintext is never stored
scopestext[]NOT NULL — see scopes
kindtoken_kindNOT NULL, default organization
organization_iduuidnullable, FK → organization (delete: cascade)
service_account_iduuidNOT NULL, FK → user (delete: cascade)
issued_by_platform_token_iduuidnullable, FK → token (delete: set null)
expires_attimestampnullable
revoked_by / revoked_datenullable — revocation is recorded, not deleted
created_by / created_atNOT NULL

Two CHECKs separate the credential families: an organization token carries an organization_id, while a platform token carries none and belongs to the system:kaiten service account (token_platform_is_orgless_system); only an organization token can have been issued by a platform token (token_platform_has_no_parent). That lineage is what lets revoking a platform credential cascade to the organization tokens it minted. Authentication covers the three families.

Connectors and CRM integrations

connector

The global registry of connector builds — one row per connector, not per organization, and so without an organization_id.

ColumnTypeConstraints
nametextPK
versiontextNOT NULL
settings_schemajsonbNOT NULL — must be a JSON object
entitlement_slugtextnullable — the slug of the BOOLEAN entitlement an organization's License must grant before it may activate this connector; NULL means ungated
created_at / updated_attimestampNOT NULL

organization_connector

Which organizations activated which connector — the fact the settings store cannot answer on its own.

ColumnTypeConstraints
organization_id + connector_namecomposite PK; FK → organization (delete: cascade), FK → connector (delete: cascade)
activated_attimestamptzNOT NULL, default now
created_at / updated_attimestamptzNOT NULL

The row answers "activated?"; the connector's per-organization settings live in the secret store, not here. An activation survives neither its organization nor the connector registration it depends on.

customer_integrations / instance_integrations

Per-adapter links between a Kaiten record and an external system (e.g. Attio).

ColumnTypeConstraints
customer_id (or instance_id) + adaptercomposite PK
external_idtextNOT NULL; (organization_id, adapter, external_id) unique
metadatajsonbNOT NULL, default {}
synced_attimestamptzNOT NULL
last_errortextnullable
web_urltextnullable — deep link into the external tool

The schema also carries migration bookkeeping not listed here. What is listed is the model the API exposes and the docs reason about.

Where to go next

On this page