Real-time updates
Cortex pushes changes to you over Server-Sent Events. The SDK wraps this in a
Firestore-style watch() API: you get the current state immediately (over REST) and then a
live stream of changes — with reconnection handled for you. No polling, no manual
EventSource, no merge logic.
Watch a live transcript
The most common real-time use case: render a transcript as people speak.
Each snapshot gives you the full current array plus the changes that triggered it, so you can either re-render everything or apply incremental updates.
With an API key, the stream only carries events for the resource families the key’s
scopes cover. Watching sessions needs sessions;
watching a transcript also needs messages. If a watcher never fires, check the key’s scopes
first.
Watch a collection
Watch every session in a project and react as they’re created, started or stopped:
The same pattern works for project.participants.watch(),
project.sanctions.watch(), project.gatherings.watch() and
project.webhooks.watchEvents().
Watch nested collections
Some collections live on a live object you fetch first:
Resilience
- All watchers in a project share a single SSE connection (closed when the last one unsubscribes).
- The connection auto-reconnects with backoff, and on every reconnect each subscription re-fetches its snapshot — so a dropped connection never leaves you with stale data.
- An idle-timeout guards against dead sockets; you can tune it via
realtime.idleTimeoutMson the client (default 45s,0to disable).
watch() works with both API-key and JWT auth, and long-lived streams with an API key are
fully supported. Credentials always travel in request headers — the SDK streams over fetch
rather than EventSource, which is why it requires a WHATWG fetch (the global fetch on
Node 18+ or any modern browser). Each (re)connect counts against the key’s
rate limit; the SDK backs off between attempts.
Polling instead of streaming
Sometimes a long-lived stream isn’t an option — a serverless backend, a cron job, or a game
server that checks in every few seconds. The transcript endpoint is built for that: every
message has a per-session sequence number (seq), so you can ask for “everything after the
last message I saw” and let the server tell you cheaply when there is nothing new.
How it works:
afterreturns only messages with a higherseq;limitis 1–200 (default 50). Useorder: "desc"to get the newest page first instead (no continuation in that order).- The response carries
latestSeq— the session’s newestseq— and sends it as theETag. When more messages are waiting than fit in one page,nextCursoris set; keep reading from the lastseqyou processed until it isnull. Passing it back asifNoneMatch(If-None-Matchover raw HTTP) makes the server answer304 Not Modifiedwithout touching the database while nothing changed. include: ["annotations"]attaches plugin annotations (e.g. toxicity) to each message, andinclude: ["debugAudio"]thehasDebugAudioflag. Both are omitted unless you ask. Because annotations arrive asynchronously, noETagis sent when you include them.- A
304still counts as a request against your rate limit. A poll every second or two per session is fine; for many sessions at once, prefer the stream.
The same over raw HTTP:
Don’t need the SDK?
You can consume the raw SSE stream directly — see
Events & Real-time. Send your API key in a header
(X-API-Key); keys are not accepted as a URL query parameter. For durable, retried delivery,
use webhooks instead.