GET /flows/executions/{id}, watch a run live over Server-Sent Events. The stream replays the full timeline on connect, follows the execution live, and closes cleanly on the terminal event — so a detail view can render its entire timeline from the stream alone.
Two endpoints stream events:
GET /flows/executions/{execution_id}/stream— one execution, with per-step detail.GET /flows/batches/{batch_id}/stream— a batch plus each child execution’s lifecycle (no per-step events).
Why a stream token
A browserEventSource cannot send Authorization / X-API-Key headers on the streaming GET. So the stream is gated by a short-lived (60-second) stream token passed as a ?token= query parameter, minted from POST /account/auth/stream-token. The token is a self-issued HS256 JWT bound to your org_id and the specific resource; it is stateless, and reusable within its 60s TTL so browser-native reconnect works.
Step 1 — Mint a stream token
POST /account/auth/stream-token with the resource you want to watch. Any authenticated principal (X-API-Key or Clerk JWT, any role) may mint a token for a resource it owns. Ownership is verified 404-not-403 — a missing resource and a cross-tenant resource both return 404.
Step 2 — Connect and reconnect (the client contract)
The wrapper below is the canonical browser client. It mints a token, opens anEventSource with the token as a query parameter, dedups by (entity_id, sequence), re-mints on token expiry, and — critically — calls EventSource.close() on the terminal event so the browser does not auto-reconnect forever.
Do not redesign this contract. Within the 60s TTL, browser-native
EventSource auto-reconnect just works: the browser resends the last frame’s id as the Last-Event-ID header. How the server uses that header depends on the stream — an execution stream resumes exactly from that sequence (a precise $gt cursor), while a batch stream cannot resume independent per-entity counters from a single header, so it always replays the full retained backlog. Past expiry the stream returns 401; the wrapper re-mints a fresh token and opens a new connection that carries no Last-Event-ID, so the full backlog replays again. Because reconnects can replay already-seen frames, the client must dedup by (entity_id, sequence) — every frame’s id is {entity_id}#{sequence}.Event-shape reference
Each SSE frame carriesid: {entity_id}#{sequence}, an event: type, and a JSON data: payload (entity_type, entity_id, batch_id, event_type, sequence, step_name, step_index, payload, error_type, message, truncated, created_at). There are six v1 event types:
The batch stream (
GET /flows/batches/{id}/stream) emits the batch’s own status events plus each child execution’s execution.created / execution.completed / execution.failed — but not per-step events. For per-step detail on a fanned-out record, open that child’s own GET /flows/executions/{child_id}/stream.
Connecting to an already-terminal execution replays its full history ending with the terminal event, then closes — the detail view uses the same stream path regardless of execution state (no
409/410 branch). A :keep-alive comment is emitted every 30 seconds to hold the connection open through the load-balancer idle timer.Next steps
Execution streaming
The full event taxonomy, reconnect semantics, and truncation contract.
Batches and executions
The lifecycle and statuses the stream surfaces live.