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_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxThe 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 1716552134000pulsefires every 15 seconds with the currently cached state. Use it as a heartbeat — if you stop seeingpulseevents for 60 seconds, your connection is broken.deltafires when the cachedgeneratedAtadvances (a fresh compaction landed). React todeltato update the agent's working memory.errorfires on non-fatal errors. The stream stays open. Multipleerrorevents without anypulsemeans 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
\nComment 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.