Kaiten

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.

The console's dashboard with the bell open: a Notifications panel showing an unread count, the latest notifications grouped under Today and Yesterday, a mark-all-as-read control, and a View all notifications linkThe console's dashboard with the bell open: a Notifications panel showing an unread count, the latest notifications grouped under Today and Yesterday, a mark-all-as-read control, and a View all notifications link

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.

GroupEvents
instancesINSTANCE_CREATED, INSTANCE_DELETED, INSTANCE_MIGRATED, INSTANCE_LIFECYCLE_STAGE_CHANGED
Off by default: INSTANCE_UPDATED, INSTANCE_STATUS_CHANGED
deploymentsINSTANCE_DEPLOYED, RELEASE_CREATED, RELEASE_DEPLOYED
Off by default: RELEASE_DELETED, DEPLOYMENT_ZONE_CREATED, DEPLOYMENT_ZONE_DELETED, COMPONENT_CREATED, COMPONENT_UPDATED
usageINSTANCE_ENTITLEMENT_USAGE_WARNING_THRESHOLD_REACHED, INSTANCE_ENTITLEMENT_USAGE_REACHED, INSTANCE_ENTITLEMENT_CAP_EXCEEDED, ENTITLEMENT_USAGE_REPORT_REJECTED
Off by default: INSTANCE_ENTITLEMENT_USAGE_PERIOD_ROLLED_OVER
customersCUSTOMER_CREATED, CUSTOMER_DELETED, CUSTOMER_CREATION_REJECTED
Off by default: CUSTOMER_UPDATED
licensingLICENSE_CREATED, LICENSE_DELETED
Off by default: LICENSE_UPDATED, LICENSE_ENTITLEMENT_ASSIGNED, LICENSE_ENTITLEMENT_UNASSIGNED
securitySYSTEM_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.

OperationScopeWhat it does
GET /v1/notificationsread:notificationsOne 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-readwrite:notificationsMarks 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-preferencesread:notificationsEvery notifiable event with its label, its group, and the caller's effective value per channel
PUT /v1/notification-preferenceswrite:notificationsSets 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.

The console's Notifications page: the feed grouped by day with an Unread or Read status on each row, the All and Unread filter, the Object filter, and the Mark all as read and Preferences buttonsThe console's Notifications page: the feed grouped by day with an Unread or Read status on each row, the All and Unread filter, the Object filter, and the Mark all as read and Preferences buttons

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.

On this page