Skip to main content

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.