Streaming
Windmill streams two kinds of data from a running job over Server-Sent Events (SSE):
- Progress: the status of each step of a flow as it runs, plus logs and explicit progress. Use it to show users where a long-running flow is at, from your own frontend or backend.
- Output: text that a script or AI agent step yields while it runs, such as LLM tokens. Use it to display a response as it is being generated.
| I want to... | Use |
|---|---|
| Show which step a flow is at, with logs and progress | Run the flow asynchronously, then subscribe to jobs_u/getupdate_sse |
| Get the text a script or flow yields, in one request | SSE stream webhook (jobs/run_and_stream) |
| Expose the text stream on a custom URL | HTTP route in Sync SSE mode |
| Follow an AI agent's tool calls and tokens | AI agent streaming |
Stream flow progress
Every job, and flows in particular, can be followed live with the job progress SSE endpoint:
GET /api/w/<workspace>/jobs_u/getupdate_sse/<job_id>
Each update event carries the flow_status of the flow whenever it changes.
Its modules array has one entry per top-level step, with the step id, its type (WaitingForPriorSteps, WaitingForExecutor, WaitingForEvents for a step suspended until approval, InProgress, Success or Failure) and the job id of the step once it has started.
Loops and branches also report their iteration or branch in the entry.
{
"type": "update",
"running": true,
"flow_status": {
"step": 1,
"modules": [
{ "type": "Success", "id": "a", "job": "0199...", "skipped": false },
{ "type": "InProgress", "id": "b", "job": "0199..." },
{ "type": "WaitingForPriorSteps", "id": "c" }
]
// other flow_status fields omitted
}
}
The last event has "completed": true and a job field with the completed job, including its result.
The full list of fields is in Job progress event response.
To stream the progress of a flow you trigger:
- Start it with the asynchronous webhook, which returns the job id right away.
The path is
jobs/run/f/<flow_path>, so a flow atf/examples/onboardingis started withjobs/run/f/f/examples/onboarding. - Open the SSE stream on that job id.
Pass
fast=trueso the server checks for updates every 100ms, then every 500ms, before settling at every 3 seconds (without it, it checks every 3 seconds from the start). Passno_logs=trueif you only need step statuses.
- curl
- TypeScript (browser)
BASE="https://app.windmill.dev/api/w/<workspace>"
JOB_ID=$(curl -s -X POST "$BASE/jobs/run/f/f/examples/onboarding" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"email": "jane@example.com"}')
curl -N "$BASE/jobs_u/getupdate_sse/$JOB_ID?fast=true&no_logs=true" \
-H "Authorization: Bearer $TOKEN"
const base = 'https://app.windmill.dev/api/w/<workspace>';
const jobId = await fetch(`${base}/jobs/run/f/f/examples/onboarding`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'jane@example.com' })
}).then((r) => r.text());
// EventSource cannot set headers, so the token is passed as a query arg
const events = new EventSource(
`${base}/jobs_u/getupdate_sse/${jobId}?fast=true&no_logs=true&token=${token}`
);
events.onmessage = (e) => {
const update = JSON.parse(e.data);
if (update.type !== 'update') return; // ping, timeout, notfound, error
for (const step of update.flow_status?.modules ?? []) {
console.log(step.id, step.type); // e.g. "b InProgress"
}
if (update.completed) {
console.log('result', update.job.result);
events.close();
}
};
Add get_progress=true to also receive the progress percentage set from code with explicit progress.
The same endpoint works for scripts, where new_logs gives the logs as they are written.
The SSE stream webhook does not include flow_status: it only sends the result stream and the final result.
Use the two-step approach above when you need step-level progress.
Stream output
Job result streaming
Scripts in Python and TypeScript can stream back results as a text stream. The stream exists while the job is running, and the full content becomes the result once the job completes. In a flow, the stream comes from the last step, or from the early return step when one is set.
AI agent streaming
AI agent steps support streaming token deltas, tool calls and tool results as structured JSON payloads.
Consuming an output stream
Output streams can be consumed from:
- SSE stream webhooks, which trigger the job and return the stream in a single request.
- HTTP routes in Sync SSE mode, to serve the stream on a custom path.
- The job progress SSE endpoint, where it arrives as
new_result_streamalongside the flow progress.