kisenon

Event streams

Server-sent events for org and project activity.

Kisenon streams organization and project activity as Server-Sent Events. Open a long-lived HTTP connection, set Accept: text/event-stream, and the control plane pushes a JSON envelope every time something changes.

The console drives its live activity views off the very same stream.

Endpoints

Two routes, both SSE:

RouteScope
GET /v1/eventsEvery project in the caller's organization.
GET /v1/projects/{id}/eventsA single project.

Both live under https://api.test.kisenon.com. The project-scoped route 404s if the project does not belong to your organization, so existence never leaks across orgs.

Auth

Same Bearer credential as every other control-plane call: an API key (nsk_…) or a cp-signed web JWT. See Auth. The caller must be a member of the organization; non-members get a 403.

Event envelope

Every event is one JSON object with a stable shape:

FieldTypeMeaning
idstringULID (26 chars). Monotonic; doubles as the SSE event id.
typestringEvent type, e.g. operation.updated.v1.
sourcestringProducing subsystem, e.g. operations.
org_idstringOwning organization (UUID text).
project_idstring?Owning project; omitted for org-level events.
region_idstringRegion that emitted the event.
atstringRFC 3339 timestamp.
dataobjectType-specific payload.

The envelope shape never changes incompatibly. New fields are additive, and new event variants get a new type (e.g. a future operation.updated.v2) rather than mutating an existing one.

Event types

Today the control plane publishes one application event type:

  • operation.updated.v1 (source operations) — an operation changed lifecycle state. data carries operation_id, action, status, and, when the row is still present, branch_id, endpoint_id, error, and initiator.

The broker also emits three internal control events (source _broker):

  • resume.gap.v1 — your Last-Event-ID is older than the broker's buffer; events were missed. Refetch full state via REST, then keep streaming.
  • overflow.v1 — your client read too slowly and was dropped; data.dropped counts the lost events. Reconnect and refetch full state.
  • error.v1 — a producer-side error marker.

The audit, invitations, and billing sources are reserved but do not emit events yet; this list grows as those tracks ship.

Resuming

Each event's id is its SSE id, so a native EventSource automatically resends the last one as a Last-Event-ID header on reconnect, and the broker replays everything buffered after it. You can also pass it explicitly:

Last-Event-ID: 01JX5N4R8ZT2W7Q9V3B1C6D8EF

If the gap is wider than the broker's in-memory ring, you receive a resume.gap.v1 instead of a replay. Treat that (and overflow.v1) as "you missed events": refetch the affected state over the REST API, then resume streaming from the latest id.

Example

curl -N -H "Authorization: Bearer $KISENON_API_KEY" \
  -H "Accept: text/event-stream" \
  https://api.test.kisenon.com/v1/events

A streamed event looks like:

id: 01JX5N4R8ZT2W7Q9V3B1C6D8EF
event: operation.updated.v1
data: {"id":"01JX5N4R8ZT2W7Q9V3B1C6D8EF","type":"operation.updated.v1","source":"operations","org_id":"6f1d2c3b-4a59-4e87-9b10-2d3e4f5a6b7c","project_id":"prj_4c1d9e2a7b3f5c8d0e1f2a3b","region_id":"home-proxmox","at":"2026-06-03T12:00:00Z","data":{"operation_id":"op_77a1","action":"create_branch","status":"finished","branch_id":"br_91c2"}}

Lines beginning with : (e.g. : keepalive) are heartbeat comments; ignore them.

In the console

The console's live activity views consume this same stream (proxied through the console origin, since a browser EventSource cannot set an Authorization header). On resume.gap.v1 or overflow.v1 it refetches full state over REST — exactly the contract above. If you are building your own consumer, mirror that behaviour.