SSE subscribe

/v1/subscribe is a long-lived Server-Sent Events stream that pushes pack pulses as soon as they land. Most agents poll /v1/fresh every 5 minutes — that's fine for short-running scripts. Long-running services should subscribe instead and let Cloven push.

Why SSE (not WebSocket, not webhooks)

  • SSE survives proxies. It's plain HTTP. Any reverse proxy that handles request/response handles SSE.
  • No upstream callback infrastructure. Webhooks would require every customer to expose a public HTTPS endpoint. SSE keeps the connection client-initiated.
  • Reconnection is free. EventSource auto-reconnects on connection loss with exponential backoff baked in.
  • WebSockets are overkill. Cloven never needs to receive client messages on the stream — the channel is one-way.

WebSocket transport is on the Phase 2 roadmap if a pack ships that needs bidirectional flow.

Auth — token query param

The browser EventSource API cannot set arbitrary headers. The API key is therefore passed as a token query parameter:

GET /v1/subscribe?pack=crypto&token=cv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

The token is validated identically to a Bearer header: same SHA-256 hash, same RLS, same rate-limit consume. Treat the URL as a secret — SSE tokens leak through server logs, proxy buffers, and shared screenshots if not handled carefully.

Server-side environments that can set headers should still use the query-param mode for consistency; the endpoint does not currently honour Authorization.

x402 is not supported on SSE. The wire model has no per-call payment hook on a long-lived stream, and we won't fake one.

Event types

event: pulse
data: { "pack": "crypto", "state": {...}, "ts": "2026-05-24T11:58:46Z" }
 
event: delta
data: { "pack": "crypto", "state": {...}, "ts": "2026-05-24T12:03:46Z" }
 
event: error
data: { "message": "redis connection lost (recovering)" }
 
: keepalive 1716552134000
  • pulse fires every 15 seconds with the currently cached state. Use it as a heartbeat — if you stop seeing pulse events for 60 seconds, your connection is broken.
  • delta fires when the cached generatedAt advances (a fresh compaction landed). React to delta to update the agent's working memory.
  • error fires on non-fatal errors. The stream stays open. Multiple error events without any pulse means the upstream is unreachable — disconnect and back off.
  • : keepalive <ms> is a comment frame (note the leading colon). It prevents proxies from closing idle streams. Ignore it on the client.

Wire format

Each event is two lines terminated by an empty line, exactly per the SSE spec:

event: <type>\n
data: <json>\n
\n

Comment frames (keepalive) are a single line starting with : plus an empty line. Anything else in the stream is a protocol violation — drop the connection and reconnect.

Browser usage

const url = new URL("https://api.cloven.cloud/v1/subscribe");
url.searchParams.set("pack", "crypto");
url.searchParams.set("token", process.env.CLOVEN_TOKEN!);
 
const es = new EventSource(url.toString());
 
es.addEventListener("pulse", (e) => {
  const payload = JSON.parse(e.data);
  // heartbeat — also carries current state
});
 
es.addEventListener("delta", (e) => {
  const payload = JSON.parse(e.data);
  // fresh compaction landed; update agent context
});
 
es.addEventListener("error", () => {
  // EventSource will auto-reconnect; log + continue
});

Node usage

Native EventSource lands in Node 22+. For older Node, use eventsource from npm or undici's EventSource polyfill. The SDK wraps this internally — for await (const pulse of client.pack("crypto").subscribe()) is the recommended path.

Reconnection

EventSource handles reconnection automatically — it sends Last-Event-ID on retry so the server can rewind. Cloven currently does not honour Last-Event-ID (the stream is stateless: each reconnect emits a fresh pulse immediately). If you need exactly-once delivery, dedupe on ts in your client.

Recommended backoff: 1s, 2s, 4s, capped at 30s. EventSource's default is fine for most cases.

Keepalive cadence

Comment frames fire every 25 seconds. If your reverse proxy idles a connection after 30s, you're fine. If it idles aggressively (10s or less), shorten the keepalive on your side or front the stream with a proxy you control.

Stream lifetime

There is no built-in cap on stream duration. Connections stay open until the client disconnects, the server restarts (rare — Vercel functions hold for hours), or the auth token is revoked (Redis cache invalidation kicks the stream within 60s).

Each subscribe call counts as 1 quota use against your tier's per-day window — not 1 per event. We do not currently meter long-running streams against the per-call budget; that may change if abuse patterns emerge.