Last updated 2026-05-07
SSE Events
The runner exposes a Server-Sent Events stream at
GET /api/events. Every state change in every run is
multicast to all subscribers — the CLI's stax log -f,
the dashboard, and anything else you point at it.
Connection
curl -N http://localhost:4800/api/eventsHeaders:
Content-Type: text/event-streamCache-Control: no-cacheConnection: keep-aliveEvent types
| Event | Payload | Meaning |
|---|---|---|
ping | {} | Sent on connect and every 30 s as keepalive. |
run.started | { runId, run } | Workflow execution started. |
run.completed | { runId, run } | Run finished (any conclusion). |
job.started | { runId, jobId, job } | Job execution started. |
job.completed | { runId, jobId, job } | Job finished. |
step.started | { runId, jobId, stepNumber, step } | Step execution started. |
step.completed | { runId, jobId, stepNumber, step } | Step finished. |
step.log | { runId, jobId, stepNumber, line } | One stdout/stderr line from a step. |
Sample stream
event: pingdata: {}
event: run.starteddata: {"runId":"a1b2c3d4","run":{"id":"a1b2c3d4","status":"in_progress","jobs":[]}}
event: job.starteddata: {"runId":"a1b2c3d4","jobId":"build","job":{"name":"build","steps":[]}}
event: step.starteddata: {"runId":"a1b2c3d4","jobId":"build","stepNumber":1,"step":{"name":"Checkout"}}
event: step.logdata: {"runId":"a1b2c3d4","jobId":"build","stepNumber":1,"line":"Workspace already available\n"}
event: step.completeddata: {"runId":"a1b2c3d4","jobId":"build","stepNumber":1,"step":{"conclusion":"success"}}
event: job.completeddata: {"runId":"a1b2c3d4","jobId":"build","job":{"conclusion":"success"}}
event: run.completeddata: {"runId":"a1b2c3d4","run":{"status":"completed","conclusion":"success"}}Browser client
const events = new EventSource('http://localhost:4800/api/events');
events.addEventListener('run.started', (e) => { const { runId, run } = JSON.parse(e.data); console.log('Run started', runId);});
events.addEventListener('step.log', (e) => { const { runId, jobId, stepNumber, line } = JSON.parse(e.data); appendToTerminal(runId, jobId, stepNumber, line);});
events.addEventListener('run.completed', (e) => { const { run } = JSON.parse(e.data); console.log('Run', run.id, 'finished:', run.conclusion);});TypeScript types
type SSEEventType = | 'run.started' | 'run.completed' | 'job.started' | 'job.completed' | 'step.started' | 'step.completed' | 'step.log';
interface SSEEvent<T = unknown> { type: SSEEventType; runId: string; jobId?: string; stepNumber?: number; data: T;}