- Per-job stream:
GET /v1/jobs/{id}/stream. Events for one specific job. - Workspace stream:
GET /v1/events. Events for every job in your workspace.
The two streams use different event names. The per-job stream uses bare names (
progress, page, error, done) because there’s only one job it could be about. The workspace stream uses job:-prefixed names (job:progress, job:completed, job:failed) and includes a jobId field in every payload so a subscriber can route events to the right job. Don’t confuse them in SDK code.Per-job stream
Open a stream for a specific job right after you create it:
The stream terminates automatically when the job reaches a terminal state.
Use the SDK’s
stream(jobId) generator if you’re in TypeScript:
Workspace stream
GET /v1/events opens a single stream that emits events for every job in your workspace as they progress. Useful for dashboards that show “what’s happening right now” without opening one stream per job.
Events on this stream are prefixed by job ID:
Keep-alives
Both streams send a comment line (: ping\n\n) every 30 seconds so intermediate proxies and load balancers don’t kill the connection as idle. Your SSE client should ignore comment lines; most libraries do this by default.
When to use SSE vs polling vs webhooks
SSE is the right tool when you’re showing data to a human who is currently looking at it. Webhooks are the right tool when the listener is a service that keeps running regardless of who’s watching.
Next
- Webhooks: push delivery when clients aren’t online
- Async jobs: the job lifecycle

