Two interfaces, one join key
Every row carries astream field with one of two values. They are two different products:
Merged is the compression for humans; raw is everything the compression was based on. A raw row’s
triggered_revision says which merged revision it caused (or null if it changed nothing) — that is how you answer “what was this conclusion based on”.
See Events interface and Raw news interface for the payloads, and Event labels for the taxonomy both speak.
Connect
1
Mint a stream token
2
Subscribe
The stream speaks the Supabase Realtime protocol, so the simplest client is Each
@supabase/supabase-js — connect with the URL and token from the response:row is { seq, stream, event_id, revision, payload, created_at } — the payload is the full message documented on the next two pages.3
Filter client-side
The socket delivers both interfaces; keep the one you want:Filtering by symbol, company or label family is also yours to do client-side — the payload’s own
symbols, company_id and labels fields are the things to match on.4
Catch up after a disconnect
Your whole position is one integer: the highest Rows come back in
seq you have processed. On reconnect, fill the gap with the catch_up URL from the token response — the same token authorizes it:seq order; process them, then resume the socket. A consumer that stores nothing but last_seq can always recover.Delivery semantics
- At-least-once. Dedupe merged rows on
(event_id, revision)and raw rows on the payload’sid— both are stable across any re-delivery. - Rows are never edited. A changed conclusion is a new revision, never an update to an old row, so your copy can be append-only too.
seqis monotonic across the whole log;revisionis monotonic per event. Gaps are detectable and repairable from your side (delta.since_revisionon the Events interface tells you exactly which revision to backfill).- Freshness: the producer writes to the log on a ~2-minute cycle; the socket removes polling, not that cycle. End-to-end from a filing’s acceptance to a row on your socket is typically a few minutes.
The WebSocket has no server-side filtering — every subscriber sees every insert. If you want us to filter (by symbols, companies or label families) and POST to your endpoint instead, webhook subscriptions over the same log exist —
POST /v1/events/subscriptions (Events) and POST /v1/news/subscriptions (Raw news) — with the same one-integer cursor and replay.