Running workflows from the REST API

Call the Flux HTTP API to start, poll, and stream workflow executions from any language or scripting environment.

Every capability in the flux CLI and the Python SDK is also available over HTTP. The REST API is the integration point for polyglot clients, shell scripts, CI pipelines, and any system that cannot embed the Python runtime. This page covers the endpoints you need to start an execution, check its status, stream live events, cancel it, and list historical runs.

The examples below use curl against a server running at http://localhost:8000. Substitute your server address wherever that appears.

Start the server

If you don’t already have a server running:

flux start server

The server exposes an interactive OpenAPI explorer at http://localhost:8000/docs. Browse it to see request schemas and try calls directly in the browser.

Register a workflow

Before the server can execute a workflow, it must be registered. Registration uploads the Python source file and records the workflow definition in the catalog:

POST /workflows
Content-Type: multipart/form-data

file=@my_workflow.py

Using curl:

curl -X POST http://localhost:8000/workflows \
  -F "file=@my_workflow.py"

A successful response returns a list of registered workflow objects, each with the workflow name, namespace, and version number. Re-registering the same file creates a new version entry; the server keeps all prior versions and defaults new executions to the latest.

Run a workflow

Start a new execution with a POST to the run endpoint. The path encodes the workflow’s namespace and name; the mode path segment controls how the server responds.

POST /workflows/{namespace}/{workflow_name}/run/{mode}

The {mode} segment accepts three values:

ModeBehaviour
asyncReturns immediately with the new execution context. The workflow runs in the background.
syncBlocks until the workflow reaches a terminal state, then returns the result.
streamReturns a text/event-stream response that emits events as the execution progresses.

The request body is the workflow’s JSON-encoded input, sent as application/json. Pass null when the workflow takes no input.

Async run

curl -X POST http://localhost:8000/workflows/default/ingest/run/async \
  -H "Content-Type: application/json" \
  -d '{"source": "s3://my-bucket", "batch_size": 500}'

The response body is the execution summary:

{
  "workflow_id": "abc123",
  "workflow_namespace": "default",
  "workflow_name": "ingest",
  "execution_id": "005d4b2f8a3c1e9b",
  "input": {"source": "s3://my-bucket", "batch_size": 500},
  "output": null,
  "state": "SCHEDULED",
  "current_worker": ""
}

Save the execution_id; you will use it to check status, poll for completion, or cancel the execution.

Sync run

curl -X POST http://localhost:8000/workflows/default/generate_report/run/sync \
  -H "Content-Type: application/json" \
  -d '"Q1-2026"'

The connection stays open until the workflow finishes. The response body has the same shape as the async summary, with state set to COMPLETED (or FAILED) and output populated with the workflow’s return value.

Use sync mode for short-lived workflows in scripts where you need the result inline and the total runtime is predictable.

Pin a specific version

Append ?version=<n> to target a registered version other than the latest:

curl -X POST "http://localhost:8000/workflows/default/ingest/run/async?version=3" \
  -H "Content-Type: application/json" \
  -d '{"source": "s3://archive"}'

Detailed response

Append ?detailed=true to any run request to receive the full execution object instead of the summary. The detailed response includes the events array: every state transition, task start, task completion, error, retry, and checkpoint recorded so far.

curl -X POST "http://localhost:8000/workflows/default/ingest/run/sync?detailed=true" \
  -H "Content-Type: application/json" \
  -d '{"source": "s3://my-bucket"}'

Check execution status

Poll for the current state of any execution using its ID:

GET /workflows/{namespace}/{workflow_name}/status/{execution_id}
curl http://localhost:8000/workflows/default/ingest/status/005d4b2f8a3c1e9b

The response is the same summary object returned by the run endpoint. When the execution finishes, output contains the workflow’s return value and state is COMPLETED, FAILED, or CANCELLED.

Add ?detailed=true to include the full event log:

curl "http://localhost:8000/workflows/default/ingest/status/005d4b2f8a3c1e9b?detailed=true"

A simple poll loop in shell:

EXECUTION_ID="005d4b2f8a3c1e9b"
while true; do
  STATE=$(curl -s http://localhost:8000/workflows/default/ingest/status/$EXECUTION_ID \
    | jq -r '.state')
  echo "State: $STATE"
  case $STATE in COMPLETED|FAILED|CANCELLED) break ;; esac
  sleep 2
done

Stream execution events with SSE

The stream mode returns a Server-Sent Events response. The server pushes one event per state transition and one task.progress event for each progress update a task emits.

POST /workflows/{namespace}/{workflow_name}/run/stream

Each SSE frame carries an event field and a data field. The event name follows the pattern {workflow_name}.execution.{state} — for example ingest.execution.running or ingest.execution.completed. Progress events use the fixed name task.progress.

curl -N -X POST http://localhost:8000/workflows/default/ingest/run/stream \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{"source": "s3://my-bucket"}'

A typical session looks like:

event: ingest.execution.scheduled
data: {"execution_id": "005d4b2f...", "state": "SCHEDULED", ...}

event: ingest.execution.claimed
data: {"execution_id": "005d4b2f...", "state": "CLAIMED", ...}

event: task.progress
data: {"type": "increment", "source_id": "task-1", "name": "rows_processed", "value": 250, "time": "..."}

event: ingest.execution.completed
data: {"execution_id": "005d4b2f...", "state": "COMPLETED", "output": {"rows": 1000}, ...}

The stream closes automatically once the execution reaches a terminal state (COMPLETED, FAILED, or CANCELLED).

Consuming SSE from Python

The httpx library with httpx-sse handles SSE natively:

import httpx
from httpx_sse import connect_sse

with httpx.Client() as client:
    with connect_sse(
        client,
        "POST",
        "http://localhost:8000/workflows/default/ingest/run/stream",
        headers={"Content-Type": "application/json"},
        content='{"source": "s3://my-bucket"}',
    ) as event_source:
        for event in event_source.iter_sse():
            print(f"[{event.event}] {event.data}")

Consuming SSE from JavaScript

// EventSource is GET-only; use fetch for POST streams
const response = await fetch(
  'http://localhost:8000/workflows/default/ingest/run/stream',
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ source: 's3://my-bucket' }),
  }
);

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  console.log(decoder.decode(value));
}

Cancel a running execution

GET /workflows/{namespace}/{workflow_name}/cancel/{execution_id}
curl http://localhost:8000/workflows/default/ingest/cancel/005d4b2f8a3c1e9b

The server transitions the execution to CANCELLING, signals the worker, and the execution reaches CANCELLED once the worker unwinds. Cancelling an already-finished execution returns an error.

List executions

Two endpoints query the execution history.

All executions across all workflows:

GET /executions?namespace=default&workflow_name=ingest&state=COMPLETED&limit=20&offset=0
curl "http://localhost:8000/executions?workflow_name=ingest&state=FAILED&limit=10"

Executions for one specific workflow:

GET /workflows/{namespace}/{workflow_name}/executions?state=RUNNING&limit=50&offset=0
curl "http://localhost:8000/workflows/default/ingest/executions?state=RUNNING"

Both endpoints return a paginated response:

{
  "executions": [
    {
      "execution_id": "005d4b2f8a3c1e9b",
      "workflow_id": "abc123",
      "workflow_namespace": "default",
      "workflow_name": "ingest",
      "state": "COMPLETED",
      "worker_name": "worker-1"
    }
  ],
  "total": 47,
  "limit": 10,
  "offset": 0
}

Valid values for the state filter: CREATED, SCHEDULED, CLAIMED, RUNNING, COMPLETED, FAILED, CANCELLED, PAUSED, RESUMING.

Get a single execution by ID

GET /executions/{execution_id}
curl "http://localhost:8000/executions/005d4b2f8a3c1e9b?detailed=true"

This endpoint looks up an execution directly by ID without requiring the namespace or workflow name, which is useful when you only have the execution ID.

Authentication

When the server runs with auth enabled ([flux.security.auth] enabled = true in flux.toml), every request must include a bearer token:

curl -X POST http://localhost:8000/workflows/default/ingest/run/async \
  -H "Authorization: Bearer <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{"source": "s3://my-bucket"}'

Generate an API key via flux principals or the admin API. The operator role covers workflow run, status, and cancel operations. See Reference: REST API → executions for the complete permission matrix.

What’s next