Core Concepts
Notifications
The in-app feed — each user's view over the organization's audit trail, with a read state and per-event preferences, in the console and over four API operations.
A notification is an audit-trail entry seen by one user. Kaiten stores no notification of its own: the feed reads the organization's trail, keeps the events that user receives, and adds that user's read state. The id of a notification is the id of the trail entry it reports, so the feed and the trail cannot disagree about what happened.
In the console, the bell in the header carries the unread count and opens the latest notifications.
What notifies
Of the 55 events Kaiten emits, 29 can become a notification. Each belongs to a group — the group the preferences return — and is on or off by default.
| Group | Events |
|---|---|
instances | INSTANCE_CREATED, INSTANCE_DELETED, INSTANCE_MIGRATED, INSTANCE_LIFECYCLE_STAGE_CHANGEDOff by default: INSTANCE_UPDATED, INSTANCE_STATUS_CHANGED |
deployments | INSTANCE_DEPLOYED, RELEASE_CREATED, RELEASE_DEPLOYEDOff by default: RELEASE_DELETED, DEPLOYMENT_ZONE_CREATED, DEPLOYMENT_ZONE_DELETED, COMPONENT_CREATED, COMPONENT_UPDATED |
usage | INSTANCE_ENTITLEMENT_USAGE_WARNING_THRESHOLD_REACHED, INSTANCE_ENTITLEMENT_USAGE_REACHED, INSTANCE_ENTITLEMENT_CAP_EXCEEDED, ENTITLEMENT_USAGE_REPORT_REJECTEDOff by default: INSTANCE_ENTITLEMENT_USAGE_PERIOD_ROLLED_OVER |
customers | CUSTOMER_CREATED, CUSTOMER_DELETED, CUSTOMER_CREATION_REJECTEDOff by default: CUSTOMER_UPDATED |
licensing | LICENSE_CREATED, LICENSE_DELETEDOff by default: LICENSE_UPDATED, LICENSE_ENTITLEMENT_ASSIGNED, LICENSE_ENTITLEMENT_UNASSIGNED |
security | SYSTEM_ORGANIZATION_TOKEN_ISSUED |
The other 26 events never notify, and stay readable in the audit trail. Three of them are recorded once per request or per report and would bury the rest: FEATURE_FLAG_EVALUATED, ENTITLEMENT_VALUE_GET, and ENTITLEMENT_USAGE_REPORT_ACCEPTED. The others are changes to the entitlement catalog and its groups, to feature flags, and to metadata fields; the License lifecycle — a version published, archived, or unarchived — and the License family events; and LICENSE_ENTITLEMENT_UPDATED, COMPONENT_DELETED, and DEPLOYMENT_ZONE_UPDATED.
The feed over the API
Four operations, two scopes. Their paths start with /v1 — the only ones on the API that do.
| Operation | Scope | What it does |
|---|---|---|
GET /v1/notifications | read:notifications | One page of the caller's feed, newest first. status=unread keeps the unread ones; objectType, repeated for several, keeps the notifications about an instance, customer, release, deployment_zone, component, license, or token |
POST /v1/notifications/mark-read | write:notifications | Marks ids read — up to 200 per call — or everything up to now with "all": true, and answers the unread count that remains |
GET /v1/notification-preferences | read:notifications | Every notifiable event with its label, its group, and the caller's effective value per channel |
PUT /v1/notification-preferences | write:notifications | Sets the events named in the body and answers the whole matrix |
Every operation acts on the caller's own feed: there is no user to name, and no way to read someone else's. A console session and a ksh_ token are both callers, so a service account has a read state and preferences of its own.
curl "$KAITEN_API/v1/notifications?status=unread" \
-H "Authorization: Bearer $KAITEN_TOKEN"On a stack that has just loaded the Kaiten Sushi Shop dataset, the feed holds what the seed did. The response, cut to one entry — the ids, the dates, the cursor, and the count are your stack's own:
{
"data": [
{
"id": "0d6e2f5a-4b1c-4c6e-9a0f-2f8f4f1c9d3e",
"eventName": "INSTANCE_DEPLOYED",
"eventType": "com.kaiten.instance.v1.deployed",
"objectType": "instance",
"title": "Sakura Tokyo Production was deployed",
"body": "Deployed to sakura-dedicated",
"actionUrl": "/customers/instances/sakura-tokyo-prod",
"createdAt": "2026-10-14T09:12:44Z"
}
],
"nextCursor": "…",
"unreadCount": 20
}title, body, and actionUrl — a console path — are rendered when the feed is read, from the entry's payload and from the objects it names as they are now. readAt is present on a notification the caller has read and absent otherwise.
The page shape is the one exception to the API's pagination. The response carries data, nextCursor, and unreadCount: there is no hasMore, nextCursor is null on the last page, and limit runs from 1 to 50 with a default of 20. unreadCount comes with every page, follows the objectType filter, and stops counting at 100. The feed looks back 90 days; the audit trail keeps what is older.
Preferences
A preference is one user's choice for one event on one channel. One channel is served today, in_app. Reading the preferences returns the 29 events with the value in effect for the caller: their own choice where they made one, the default otherwise.
curl -X PUT "$KAITEN_API/v1/notification-preferences" \
-H "Authorization: Bearer $KAITEN_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "events": [ { "eventName": "INSTANCE_STATUS_CHANGED", "channels": { "in_app": true } } ] }'This PUT sets what it names and nothing else: an event left out of the body keeps its value. A choice equal to the default is stored as no choice, so a default that changes later still reaches that user. An event that is not notifiable, or a channel other than in_app, is refused with a 400.
Where to go next
Exact reference — the three tables behind the read state and the preferences: the data model. On a self-hosted stack, two things decide whether the feed works: the event pipeline, which writes the audit trail the feed reads, and the identity provider's scopes claim, which has to carry the two notification scopes for a console session.