Subscribing to the feed & live stream
Reading events back is a subscribe/read-plane operation — the caller must be the
subject in question, or hold an authorized group scope (see
Authz planes). There is no TS SDK wrapper for reading
today; both paths below are shown as curl/fetch.
:::caution No TS SDK read methods yet
@amphoze/cns-core defines the PageResult/FeedQuery types for
GET /v1/feed's shape, but no package in the SDK actually calls the endpoint — there
is no client.feed(...) method. Until one ships, call GET /v1/feed directly (with
your own fetch), using the types below to describe the response shape you'll
actually receive on the wire (see the caution in the next section).
:::
Paginated feed: GET /v1/feed
curl -s -H "Authorization: Bearer $TOKEN" \
"$CNS_API/v1/feed?event_type=order.shipped&since=2026-09-01T00:00:00Z&limit=50"
Query parameters: subject_id (defaults to the caller; must resolve to the caller or
an authorized group), event_type (repeatable), since/until (RFC3339), cursor
(opaque, from a prior page's NextCursor), limit.
{
"Events": [ /* event.Event, one per row — schemas/event.schema.json shape */ ],
"NextCursor": "",
"HasMore": false
}
:::caution Verified: the response is PascalCase (Events/NextCursor/HasMore)
api/handlers.go's getFeed encodes adapters/store.PageResult directly, and that
Go struct carries no JSON tags — so its fields marshal under Go's default
(exported-name-verbatim) rule: Events, NextCursor, HasMore. Both
api/ROUTES.md's prose and @amphoze/cns-core's TypeScript PageResult type
(events/next_cursor/has_more, all lowercase) currently describe the other
casing. This was confirmed by reading internal/adapters/store/store.go's struct
definition and the handler's encode call directly — it is a real drift between the
docs/SDK and the running server, not a documentation preference. If you're
hand-writing a TypeScript response type today, match the server
(Events/NextCursor/HasMore), not @amphoze/cns-core's current PageResult.
:::
An authorized group-scope feed query (subject_id resolving to a group you
belong to, not your own subject) currently returns 501 Not Implemented — the store
adapter's query shape is subject-keyed only in Phase 1.
Live stream: GET /v1/stream
curl -N -H "Authorization: Bearer $TOKEN" "$CNS_API/v1/stream"
One data: <event.Event JSON>\n\n SSE frame per matching event, filtered to your own
tenant and (by default) your own subject as actor-or-target. Pass ?channel=<scope>
for a group scope you've been granted.
:::caution Requires a configured realtime adapter, even though it's bus-transported
GET /v1/stream authorizes through two checks — CNS's own subscribe authorizer,
and the realtime adapter's own AuthorizeSubscribe callback. The noop realtime
adapter (what an unconfigured deployment runs) fails that second check closed by
design, so this endpoint returns 403 until a real realtime adapter (e.g.
Centrifugo) is configured — even though the actual event delivery happens over the
internal bus, not through the realtime adapter. See
Realtime & streaming for the full
explanation and why this is verified from source, not a guess.
:::
This is a different transport from the TypeScript client's subscribe() method
— that connects directly to a realtime-adapter WebSocket URL you configure
separately, not to this SSE endpoint. If you reach for subscribe(), read
Realtime & streaming first so you know which
one you're actually using — and that its wire protocol is still an explicitly
unconfirmed placeholder.