Skip to main content
Authenticated UI clients watch live workflow progress over Server-Sent Events instead of polling GET /executions/{id}. The stream replays the full timeline on connect, then follows the execution live, and closes cleanly on the terminal event — so a detail view can render its entire timeline from the stream alone.
This page is the conceptual reference for the event taxonomy, resume contract, and truncation behavior. The copy-paste EventSource client wrapper (mint → connect → reconnect → close) lives in the stream executions guide.
Two endpoints stream events:
  • GET /executions/{id}/stream — a single execution, with per-step detail.
  • GET /jobs/{id}/stream — a job plus each child execution’s lifecycle (no per-step events).

Stream tokens

A browser EventSource 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 /auth/stream-token.
MethodEndpointPurpose
POST/auth/stream-tokenMint a 60s token for a resource you own. Any authenticated principal (X-API-Key or Clerk JWT, any role) may mint.
GET/executions/{id}/streamStream one execution’s lifecycle. Token-only auth (?token=).
GET/jobs/{id}/streamStream a job plus each child execution’s lifecycle. Token-only auth.
The token is a self-issued HS256 JWT with claims purpose=stream, org_id, slug, rt (resource type), rid (resource id), iat, and exp = iat + 60. It is stateless — any API pod validates it without a Clerk round-trip — and reusable within its 60s TTL, so browser-native reconnect works. Ownership is verified 404-not-403 before minting: a missing resource and a cross-tenant resource both return 404.
curl -X POST "$BASE_URL/auth/stream-token" \
  -H "X-API-Key: $GETDIALED_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "resource_type": "execution", "resource_id": "exec_aabbccdd" }'

Event taxonomy (v1 — 6 types)

Each SSE frame carries an id: {entity_id}#{sequence} (the resume anchor), an event: <event_type>, and a data: <json> payload with fields like entity_type, entity_id, sequence, step_name, step_index, payload, error_type, message, and truncated.
EventMeaning
execution.createdThe execution started.
step.startedA workflow step began (step_name, step_index).
step.completedA step finished; payload carries its output (size-capped — see below).
step.failedA step failed; structured error_type + human message (never a Python traceback).
execution.completedTerminal — final result in payload; the stream closes after this frame.
execution.failedTerminal — structured error; the stream closes after this frame.
A :keep-alive comment is emitted every 30 seconds to hold the connection open through intermediary idle timeouts. Errors carried to the browser are structured — stack traces, module paths, and SOAP fault dumps stay server-side.
execution.completed and execution.failed are terminal — the server closes the stream after that frame. A browser EventSource otherwise auto-reconnects forever after a close, so the client wrapper must call EventSource.close() on the terminal event. See the stream executions guide for the full contract.

Terminal close and job streams

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). The job stream emits the job’s own status events plus each child execution’s execution.created / execution.completed / execution.failed — but not per-step events (a 1000-record fan-out emits ~2K child-lifecycle events, not ~10K+ step events). For per-step detail, open the child’s own GET /executions/{child_id}/stream. Only the job’s own terminal event closes the job stream; a child’s terminal does not. An already-terminal job (including a cancelled job) replays whatever history remains, emits a final synthetic terminal frame, and closes.

Reconnect and Last-Event-ID resume

Last-Event-ID has the form {entity_id}#{sequence} — echo the id: of the last frame you processed on reconnect.
  • Single-entity execution stream — precise resume. One monotonic per-entity counter, so Last-Event-ID={execution_id}#{N} resumes at exactly sequence > N: no gap and no re-delivery.
  • Job stream — full-backlog replay + client dedup. A job and each child have independent sequence counters, but a browser sends only one Last-Event-ID. A single scalar cursor cannot resume every entity, so the job stream replays the full job backlog on reconnect. This guarantees no missed events — but already-seen events can be re-delivered. The client MUST dedup by (entity_id, sequence): every frame’s id is {entity_id}#{sequence}, so track the rendered pairs and drop repeats.
Past the 60s token expiry the stream returns 401; the client re-mints a fresh token and reopens with its last-seen Last-Event-ID. There is no server-side grace window — the strict 60s posture stays.

Payload truncation and REST fallback

step.completed carries the step output and execution.completed carries the final result. Payloads above a size threshold are truncated with truncated: true on the event; the UI then falls back to GET /executions/{id} for the body.
Honest truncation caveat. When a result exceeds the size cap, both the streamed payload and the persisted Execution.result are truncated (same size discipline). So the GET /executions/{id} fallback returns the capped representation — not the original oversized body. The full untruncated value is retained only in the platform’s internal execution history; UIs must not assume the REST fallback recovers arbitrarily large outputs.

Next steps

Stream executions

The copy-paste EventSource wrapper: mint, connect, reconnect on 401, and close on terminal.

Jobs and executions

The underlying job and execution lifecycle the stream reports on.