Event stream
Server-sent events for every ontology change you may see, resumable by sequence number.
See Live events for delivery guarantees.
GET /v1/ontology/events
Opens a text/event-stream.
| Parameter | |
|---|---|
after | resume after this sequence number |
types | comma-separated object type api names: events about any of them |
objects | comma-separated object ids: events about any of them |
channels | comma-separated chat channel ids: only events in any of them |
Last-Event-ID (the header browsers send when an EventSource reconnects)
takes precedence over after. Without either, the stream starts at the next
change. types, objects, and channels take up to 100 values each.
curl -N "$BASE/v1/ontology/events?types=employee" \
-H "Authorization: Bearer $TOKEN" \
-H "Last-Event-ID: 1041"id: 1042
event: object.created
data: {"type":"employee","id":"o4b8j2e6c0t1q5w9r3y7u2i6","version":1,"properties":{"name":"Ada"}}
id: 1043
event: object.updated
data: {"type":"employee","id":"o4b8j2e6c0t1q5w9r3y7u2i6","version":2,"etag":"Lk2pR9cXv4bQ7mNw1sT0ya","updated_at":"2026-10-24T10:05:00Z","updated_by":"h3jd8s0q2l5x7c1v9b4n6m8k","changed":["age"],"properties":{"name":"Ada","age":36}}
id: 1047
event: object.deleted
data: {"type":"employee","id":"o4b8j2e6c0t1q5w9r3y7u2i6"}Sequence numbers are per organization and gap-free in the outbox; a stream skips the ones you may not see or filtered out, so the ids you receive can jump. Object events carry only the properties you may see: restricted properties are stripped for each subscriber when the event is read.
Event kinds
event | data |
|---|---|
object.created, object.updated | {type, id, version, etag, updated_at, updated_by, properties}, and for updates changed, the properties it changed; all as you see them, and an update that changed no property you see is not sent to you |
object.deleted | {type, id} |
link.created, link.deleted | {link, from_type, from, to_type, to} |
type.created, type.changed, type.deleted | {type, version} |
type.validation | {type, rules_version, state, objects, validated, invalid}: the background job’s progress against new rules, at most once a second (less often on large types) and once when it is done with the type |
link_type.created, link_type.changed, link_type.deleted | {link, from_type, to_type} |
team.member_added | {type, id, user, expires_at}: a member joined or was renewed on the team id, of the team type type, until expires_at or for good (null) |
team.member_removed | {type, id, user} |
series.points | {type, series, object, tags, count, first_at, last_at, points, truncated}: points a batch wrote to a series of a timeseries type with live on; at most 100 points, truncated if there were more |
chat.channel.created, chat.channel.changed, chat.channel.deleted, chat.member.added, chat.member.removed, chat.message.created, chat.message.edited, chat.message.deleted, chat.reaction.added, chat.reaction.removed, chat.pin.added, chat.pin.removed | {type, channel, ...}: a change in a chat channel, delivered only while you can read the channel |
chat.typing (no id) | {type, channel, user}: someone else is typing in a channel you can read; never stored |
fs.node.created, fs.node.changed, fs.node.moved, fs.node.deleted, fs.node.restored, fs.node.access, fs.file.written | {type, node, ...}: a change to a folder or file of a filesystem type, delivered only while you can see it |
action.applied | the result of an action, after the events of its edits |
reset (no id) | {reason}: your cursor is older than the 7 days kept; reload, then carry on |
Ending
The stream ends when your session ends, you are deactivated, or you lose
view_objects; it is re-authenticated before every read and at least every
10 seconds. Reconnect with the last id you saw.
Limits
At most 20 open streams per user on each server replica; beyond that,
opening one is 429.
From a browser
EventSource cannot send an Authorization header, so use a streaming
fetch (or an EventSource polyfill that supports headers):
const headers = { Authorization: `Bearer ${token}` };
if (lastId) headers['Last-Event-ID'] = lastId;
const res = await fetch(`${BASE}/v1/ontology/events?types=employee`, { headers });
const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();
for (;;) {
const { value, done } = await reader.read();
if (done) break;
// parse "id:", "event:", "data:" lines separated by blank lines
}