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:
| Route | Scope |
|---|---|
GET /v1/events | Every project in the caller's organization. |
GET /v1/projects/{id}/events | A 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:
| Field | Type | Meaning |
|---|---|---|
id | string | ULID (26 chars). Monotonic; doubles as the SSE event id. |
type | string | Event type, e.g. operation.updated.v1. |
source | string | Producing subsystem, e.g. operations. |
org_id | string | Owning organization (UUID text). |
project_id | string? | Owning project; omitted for org-level events. |
region_id | string | Region that emitted the event. |
at | string | RFC 3339 timestamp. |
data | object | Type-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(sourceoperations) — an operation changed lifecycle state.datacarriesoperation_id,action,status, and, when the row is still present,branch_id,endpoint_id,error, andinitiator.
The broker also emits three internal control events (source _broker):
resume.gap.v1— yourLast-Event-IDis 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.droppedcounts 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: 01JX5N4R8ZT2W7Q9V3B1C6D8EFIf 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/eventsA 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.
Related
- Auth — Bearer credentials.
- Organizations — membership and scope.