Workflow execution via REST API with Quarkus Flow Runner

Preview — This feature is functional and tested, but backward compatibility is not guaranteed across releases.

Deploy and execute Open Workflow definitions via REST API.

The Quarkus Flow Runner extension transforms your Quarkus application into a lightweight, cloud-native workflow execution engine. Load workflow definitions from YAML/JSON files at startup, and execute them through a secured REST API.

This enables runtime workflow deployment where definitions are managed externally (by CI/CD pipelines, GitOps operators, or development teams) and deployed as configuration rather than requiring application rebuilds.

Use cases

When to use the Runner:

  • GitOps workflow deployment - Store workflow definitions in Git, deploy via Kubernetes ConfigMaps, execute via API

  • Runtime workflow management - Update and deploy workflows without application rebuilds or recompilation

  • Multi-tenant workflow hosting - Deploy a single Runner instance serving workflows for multiple teams/namespaces

  • Event-driven automation - Execute workflows triggered by webhooks, Kafka events, or scheduled jobs

  • Externally-managed workflows - Workflows authored by tools, UI builders, or external systems that generate YAML definitions

  • Microservice-based architecture - Expose workflow execution as a REST API for integration with other services

When NOT to use the Runner:

  • Type-safe Java DSL preferred - Use standard Quarkus Flow workflows for compile-time safety and IDE support

  • Build-time optimization critical - Java workflows get compile-time validation, dead code elimination, and native image optimization

  • Tightly coupled workflows - Workflows that depend heavily on application CDI beans and business logic should remain as Java classes

Prerequisites

  • Java 17 or newer

  • Maven 3.8+ or Quarkus CLI

  • An existing Quarkus project, or create one:

    quarkus create app org.acme:workflow-runner
    cd workflow-runner

1. Add the Runner dependency

Add the Flow Runner extension to enable REST-based workflow execution.

pom.xml
<dependency>
  <groupId>io.quarkiverse.flow</groupId>
  <artifactId>quarkus-flow-runner</artifactId>
  <version>1.3.0</version>
</dependency>
The Runner extension automatically includes the core quarkus-flow dependency. You don’t need to add it separately.

2. Configure workflow loading

The Runner loads workflow definitions from a configured filesystem path at startup. This directory is typically a Kubernetes ConfigMap or volume mount in production.

application.properties
# Enable the runner (default: true)
quarkus.flow.runner.enabled=true

# Path to workflow definitions (REQUIRED when enabled)
# Scans recursively for .yaml, .yml, .json files
quarkus.flow.runner.source.path=/deployments/workflows
On Kubernetes, ConfigMap and Secret volume mounts use an internal symlink structure. By default, the Runner skips symbolic links during directory scanning to prevent duplicate workflow detection failures. If your workflow files are intentionally provided via symbolic links outside of a Kubernetes volume mount, set quarkus.flow.runner.source.follow-symlinks=true.

File watcher for live workflow loading

By default, the Runner loads workflow definitions once at startup. You can enable a file watcher that periodically polls the source path for new workflow files and registers them automatically — without requiring a pod restart.

application.properties
# Enable the file watcher (build-time property, default: false)
quarkus.flow.runner.source.watch.enabled=true

# Polling interval (runtime property, default: 5s)
quarkus.flow.runner.source.watch.interval=5s

When enabled, the watcher:

  • Polls the configured source.path directory at the specified interval

  • Detects new .yaml, .yml, and .json files (including in subdirectories)

  • Parses and registers new workflow definitions via the same mechanism as startup loading

  • Caches already-processed files to avoid re-parsing on every poll cycle

  • Skips malformed files with a warning and retries them on the next cycle (handles partially-written files during ConfigMap rotation)

  • Respects the follow-symlinks setting

The watch.enabled property is a build-time property — changing it requires a rebuild. The watch.interval property is a runtime property and can be changed without rebuilding.
The file watcher currently supports detecting new workflow files only. Updating or removing existing workflow definitions at runtime requires an application restart. See issue #842 for future support.
The pre-built Runner container images (quay.io/quarkiverse/quarkus-flow-runner) have the file watcher enabled by default, since they target Kubernetes deployments where ConfigMap updates are common.

Local development setup:

Create a workflows/ directory in your project:

mkdir -p src/main/resources/workflows

Point the configuration to it during development:

application.properties
quarkus.flow.runner.source.path=src/main/resources/workflows

3. Create workflow definitions

Workflow definitions follow the Open Workflow Specification (CNCF sandbox project). The Runner supports YAML and JSON formats.

Create your first workflow definition as a YAML file.

src/main/resources/workflows/hello-runner.yaml
document:
  dsl: "1.0.0"
  namespace: examples
  name: hello-runner
  version: "1.0.0"
  title: "Simple greeting workflow for the Runner"
input:
  schema:
    format: json
    document:
      type: object
      properties:
        name:
          type: string
do:
  - greet:
      set:
        greeting: "${ \"Hello, \" + .name + \"!\" }"

Key fields for the Runner:

  • document.dsl - DSL version (e.g., "1.0.3") (REQUIRED)

  • document.namespace - Organizes workflows and enforces access control (REQUIRED)

  • document.name - Workflow identifier within the namespace (REQUIRED)

  • document.version - Semantic version string for versioning support (REQUIRED)

  • document.title - Description shown in OpenAPI documentation (optional)

  • input.schema - JSON Schema for input validation (optional but recommended)

  • do - Array of tasks to execute (REQUIRED)

4. Choose a security mode

The Runner supports three authentication modes. For this tutorial, start with NONE for local testing, then configure proper authentication for production.

NONE (Development only)

This mode disables all authentication. Use only for local development. Production deployments MUST use OIDC or API_KEY authentication.
application.properties (Development)
quarkus.flow.runner.security.type=NONE

When you start the application, you’ll see a warning:

WARN  [io.qua.flo.run.sec] Flow Runner security is disabled (type=NONE).
                           This is NOT safe for production deployments.

API_KEY (Machine-to-machine)

API Key authentication is ideal for webhook integrations, CI/CD pipelines, and service-to-service communication.

application.properties (Production)
# Enable API Key authentication
quarkus.flow.runner.security.type=API_KEY

# Define API keys with roles and optional namespace restrictions
quarkus.flow.runner.security.api-keys.admin.secret=${FLOW_ADMIN_KEY}
quarkus.flow.runner.security.api-keys.admin.roles=flow-admin

quarkus.flow.runner.security.api-keys.invoker.secret=${FLOW_INVOKER_KEY}
quarkus.flow.runner.security.api-keys.invoker.roles=flow-invoker
quarkus.flow.runner.security.api-keys.invoker.namespaces=examples,team-a

Environment variables:

export FLOW_ADMIN_KEY="admin-secret-change-me"
export FLOW_INVOKER_KEY="invoker-secret-change-me"

Predefined roles:

  • flow-admin - Access to all endpoints in every namespace

  • flow-invoker - Access to all endpoints, restricted to the namespaces the key or token is authorized for

API Key usage:

curl -X POST http://localhost:8080/q/flow/exec/examples/hello-runner \
  -H "Authorization: Bearer ${FLOW_INVOKER_KEY}" \
  -H "Content-Type: application/json" \
  -d '{"name": "World"}'

OIDC (Enterprise SSO)

For enterprise deployments with existing identity providers (Keycloak, Auth0, Okta, Azure AD), use OIDC authentication.

Add the OIDC extension:

pom.xml
<dependency>
  <groupId>io.quarkus</groupId>
  <artifactId>quarkus-oidc</artifactId>
</dependency>

Configure OIDC and Runner security:

application.properties (Production)
# Enable OIDC authentication for the Runner
quarkus.flow.runner.security.type=OIDC

# Standard Quarkus OIDC configuration
quarkus.oidc.auth-server-url=https://keycloak.example.com/realms/workflows
quarkus.oidc.client-id=workflow-runner
quarkus.oidc.credentials.secret=${OIDC_CLIENT_SECRET}

# JWT claim containing authorized namespaces
quarkus.flow.runner.security.namespace.claim=namespace

JWT token requirements:

  • Must contain a roles claim with flow-admin or flow-invoker

  • A user with the flow-admin role can access all namespaces. The namespace claim is optional and is not used to restrict an administrator.

  • A non-admin user must have an authorized namespace claim when namespace validation is enabled.

  • For non-admin users, a missing, null, empty, or blank namespace claim grants no namespace access and requests to a namespace are rejected with 403 Forbidden.

  • Example token payload:

{
  "sub": "alice@example.com",
  "roles": ["flow-invoker"],
  "namespace": ["examples", "team-a"]
}

Non-admin with unrestricted namespace access:

{
  "sub": "automation@example.com",
  "roles": ["flow-invoker"],
  "namespace": ["*"]
}

Administrator without a namespace claim:

{
  "sub": "administrator@example.com",
  "roles": ["flow-admin"]
}

The administrator can access every namespace even though the token does not contain a namespace claim.

API usage:

curl -X POST http://localhost:8080/q/flow/exec/examples/hello-runner \
  -H "Authorization: Bearer ${JWT_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"name": "World"}'

Custom authentication

For advanced scenarios (mTLS, custom tokens, existing auth filters), you can combine security.type=NONE with your own JAX-RS filters.

application.properties
quarkus.flow.runner.security.type=NONE
quarkus.flow.runner.security.namespace.validate=false

Then implement a custom ContainerRequestFilter that injects SecurityIdentity. The Runner endpoints will respect @RolesAllowed annotations if a valid identity is present.

5. Start the application

Run Quarkus in development mode:

./mvnw quarkus:dev

Watch the startup logs to verify workflows loaded successfully:

INFO  [io.qua.flo.run.loa] Loading workflow definitions from: src/main/resources/workflows
INFO  [io.qua.flo.run.loa] Registered workflow: examples:hello-runner:1.0.0
INFO  [io.qua.flo.run.loa] Loaded 1 workflow definition(s)

6. Execute workflows via REST API

Synchronous execution

Execute the workflow and wait for the result (blocks until completion) using the /sync endpoint:

curl -X POST http://localhost:8080/q/flow/exec/examples/hello-runner/sync \
  -H "Content-Type: application/json" \
  -d '{"name": "World"}'

Response (200 OK):

The response body contains the workflow output directly, allowing REST clients to map it to business POJOs:

{
  "greeting": "Hello, World!"
}

Note that using sync for workflows with listen or wait tasks might lead to REST client timeouts. Therefore, it is expected that you use the async version of the API for long running workflows.

Asynchronous execution

Start the workflow and return immediately (non-blocking):

curl -X POST http://localhost:8080/q/flow/exec/examples/hello-runner \
  -H "Content-Type: application/json" \
  -d '{"name": "World"}'

Response (202 Accepted):

{
  "workflowApplicationId": "quarkus-flow-runner",
  "instanceId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "WAITING",
  "startedAt": "2026-06-11T10:15:30.123Z",
  "completedAt": null
}

API Contract:

The Runner follows the HTTP API contract strictly for asynchronous execution:

  • Always returns 202 Accepted - Regardless of how quickly the workflow completes

  • No workflow output returned - Output is only available via synchronous /sync endpoints

  • Status reflects current state - Can be WAITING, RUNNING, or COMPLETED depending on when the response was captured

This ensures clients can rely on a consistent API contract: asynchronous execution means "acknowledge receipt and start execution" with no output, while synchronous execution (using /sync endpoints) means "wait for completion and return output".

For truly long-running workflows, use asynchronous execution and poll for status or implement callbacks (future feature).

The instanceId returned by an asynchronous execution lets you track the instance with the instances endpoints and control it with the suspend, resume, and cancel endpoints. See the API reference for every supported endpoint.

Execution response fields

Synchronous and asynchronous executions return different response structures:

Asynchronous execution (202 Accepted):

Returns execution metadata without workflow output:

Field Type Description

workflowApplicationId

string

Identifier of the workflow application (the Runner instance) that executed the workflow. It is derived from quarkus.application.name when set; otherwise the engine generates one. Use it to correlate an execution with the Runner instance that processed it, for example when several Runner replicas sit behind a gateway. The same identifier is exposed by the health check and the OpenTelemetry flow.application.id span attribute.

instanceId

string

Unique identifier of the workflow instance.

status

string

Current status of the instance (WAITING, RUNNING, COMPLETED, FAULTED, …​).

startedAt

string (ISO-8601)

Timestamp when the instance started.

completedAt

string (ISO-8601) or null

Timestamp when the instance completed, or null if it is still running.

Set quarkus.application.name in application.properties to control the workflowApplicationId reported by asynchronous executions.

Synchronous execution (200 OK):

Returns the workflow output directly in the response body. The structure depends on your workflow definition and can be any valid JSON type (object, array, string, number, boolean, or null). This allows REST clients to directly map the response to business POJOs without unwrapping.

Execute specific workflow version

If you have multiple versions of the same workflow, specify the version in the path:

curl -X POST http://localhost:8080/q/flow/exec/examples/hello-runner/1.0.0/sync \
  -H "Content-Type: application/json" \
  -d '{"name": "World"}'

When you omit the version (/exec/{namespace}/{name}/sync or /exec/{namespace}/{name}), the Runner automatically selects the latest version based on semantic versioning.

7. Explore the OpenAPI documentation

The Runner automatically generates OpenAPI documentation for all loaded workflows. This provides a live API catalog and interactive testing interface.

Access the Swagger UI:

  1. Ensure the OpenAPI extension is added to your project:

    pom.xml
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-smallrye-openapi</artifactId>
    </dependency>
  2. Start Quarkus in dev mode and open: http://localhost:8080/q/swagger-ui

  3. Find the Flow Runner Execution section to see all registered workflows as individual operations

OpenAPI spec download:

# JSON format
curl http://localhost:8080/q/openapi -o openapi.json

# YAML format
curl -H "Accept: application/yaml" http://localhost:8080/q/openapi -o openapi.yaml

Dynamic workflow operations:

By default (quarkus.flow.runner.openapi.expand-workflows=true), the Runner generates a dedicated operation for each registered workflow. This provides better documentation but exposes the workflow catalog publicly.

To disable per-workflow operations and only document the generic parameterized endpoint:

application.properties
quarkus.flow.runner.openapi.expand-workflows=false
The OpenAPI document is publicly accessible even when authentication is enabled. It shows workflow metadata (namespace, name, version, summary) but does not allow execution without proper credentials.

Security in depth

Role-based access control (RBAC)

Every Runner endpoint under /q/flow requires one of two predefined roles:

Role Endpoints Namespace restrictions

flow-admin

All Runner endpoints

None. Administrators can access every namespace, with or without a namespace claim.

flow-invoker

All Runner endpoints (execution, instance lifecycle, definitions, instances)

Limited to the authorized namespaces when namespace validation is enabled (see below).

Requests without a valid identity are rejected with 401 Unauthorized. Configure roles per API key or extract them from OIDC JWT tokens.

Namespace-based authorization (ABAC)

Beyond role-based access, the Runner enforces attribute-based access control at the namespace level. Users can only execute workflows in namespaces they are authorized for.

How it works:

  1. The workflow path contains a namespace parameter: /q/flow/exec/{namespace}/…​

  2. The Runner extracts the user’s authorized namespaces from their authentication context

  3. If the requested namespace is not in the user’s authorized list, the request is rejected with 403 Forbidden

Namespace configuration:

For API_KEY mode, configure namespaces per key:

application.properties
# Allow this key to execute workflows in "team-a" and "team-b" only
quarkus.flow.runner.security.api-keys.team-key.secret=${TEAM_KEY}
quarkus.flow.runner.security.api-keys.team-key.roles=flow-invoker
quarkus.flow.runner.security.api-keys.team-key.namespaces=team-a,team-b

For OIDC mode, namespaces are extracted from JWT claims:

application.properties
# JWT claim name containing authorized namespaces (default: "namespace")
quarkus.flow.runner.security.namespace.claim=namespace

The claim can contain:

  • Single namespace: "namespace": "team-a"

  • Array of namespaces: "namespace": ["team-a", "team-b"]

  • Comma-separated string: "namespace": "team-a,team-b"

  • Wildcard *, which grants access to all namespaces: "namespace": "*". The wildcard can also be included in an array: "namespace": ["*"]

Only the exact namespace value * grants access to all namespaces. Namespace matching is exact and case-sensitive. Values such as team-*, my*, **, or " * " are treated as ordinary namespace names and do not act as wildcard patterns.

Disabling namespace validation:

If your deployment doesn’t require namespace-based authorization (ABAC), you can disable it:

application.properties
quarkus.flow.runner.security.namespace.validate=false

When disabled, all authenticated users can access workflows in all namespaces. Only role-based authorization (RBAC) is enforced.

Public OpenAPI document

When quarkus-smallrye-openapi is present, the OpenAPI document (/q/openapi) and Swagger UI are served without authentication to enable API discovery. With quarkus.flow.runner.openapi.expand-workflows=true (the default), the document includes one operation per registered workflow.

What the OpenAPI document exposes:

  • Workflow identifiers (namespace:name:version)

  • Titles and summaries

  • Input schemas (for client-side validation)

What is protected (requires authentication):

  • Every Runner endpoint under /q/flow, including GET /q/flow/definitions

  • Workflow execution and instance lifecycle operations

  • Full workflow definitions (YAML/JSON source)

To hide the workflow catalog from the OpenAPI document, set quarkus.flow.runner.openapi.expand-workflows=false, or restrict /q/openapi and /q/swagger-ui at an API gateway.

API reference

Endpoint overview

All endpoints are served under /q/flow and require the flow-admin or flow-invoker role (see 4. Choose a security mode). When namespace validation is enabled, the {namespace} path parameter (or the namespace query parameter) is checked against the caller’s authorized namespaces.

Method Path Purpose

POST

/q/flow/exec/{namespace}/{name}[/{version}]

Start a workflow instance asynchronously (202 Accepted)

POST

/q/flow/exec/{namespace}/{name}[/{version}]/sync

Run a workflow instance and wait for its output (200 OK)

POST

/q/flow/exec/{namespace}/{name}[/{version}]/{instanceId}/suspend

Suspend a running instance

POST

/q/flow/exec/{namespace}/{name}[/{version}]/{instanceId}/resume

Resume a suspended instance

POST

/q/flow/exec/{namespace}/{name}[/{version}]/{instanceId}/cancel

Cancel an instance

GET

/q/flow/definitions

List workflow definitions

GET

/q/flow/definitions/{namespace}/{name}[/{version}]

Get a workflow definition as YAML or JSON

GET

/q/flow/instances

List active instances on this Runner

GET

/q/flow/{namespace}/{name}[/{version}]/instances

List active instances of one workflow on this Runner

Segments in square brackets are optional: when {version} is omitted, the Runner resolves the latest registered version (see Multiple workflow versions).

Common conventions

Authentication: Send credentials in the Authorization header (Authorization: Bearer <api-key-or-jwt>). With security.type=NONE, no header is required.

Request bodies: Execution endpoints accept Content-Type: application/json. The body is the workflow input and may be any JSON value (object, array, string, number, boolean). It is optional.

Common error responses:

Status Meaning

400 Bad Request

Malformed JSON body, or an invalid query parameter value (for example, a terminal status filter on the instances endpoints).

401 Unauthorized

Missing or invalid credentials.

403 Forbidden

The caller is authenticated but not authorized for the requested namespace.

404 Not Found

The workflow definition, version, or instance does not exist on this Runner. The body is a plain-text message, for example Workflow instance '<id>' not found.

415 Unsupported Media Type

The Accept header of a definition request is neither application/json nor application/yaml.

Workflow execution

POST /q/flow/exec/{namespace}/{name}

Execute the latest version of a workflow asynchronously (non-blocking).

Path parameters:

  • namespace (string, required) - Workflow namespace

  • name (string, required) - Workflow name

Query parameters:

  • wait (boolean, optional, default false) - Deprecated. When true, the request blocks until the workflow completes and returns 200 OK with the execution metadata plus a workflowOutput field. Use the /sync endpoints instead.

Request body (JSON, optional):

{
  "orderId": "12345",
  "customerId": "alice@example.com"
}

Responses:

  • 202 Accepted - Workflow accepted for asynchronous processing. Body: see Execution response fields

  • 400 Bad Request - Invalid input JSON

  • 401 Unauthorized - Authentication required

  • 403 Forbidden - Access denied to namespace

  • 404 Not Found - No versions of the workflow found

This endpoint strictly returns 202 Accepted without workflow output, even if the workflow completes before the response is sent. This ensures a consistent contract where async execution never includes output in the response.
Keep the instanceId from the response. You need it to suspend, resume, or cancel the instance (see Instance lifecycle (suspend, resume, cancel)).

Example:

curl -X POST "http://localhost:8080/q/flow/exec/examples/order-processing" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${API_KEY}" \
  -d '{"orderId": "12345", "customerId": "alice@example.com"}'

POST /q/flow/exec/{namespace}/{name}/{version}

Execute a specific version of a workflow asynchronously (non-blocking).

Path parameters:

  • namespace (string, required) - Workflow namespace

  • name (string, required) - Workflow name

  • version (string, required) - Workflow version (semantic version format)

Request body (JSON, optional): Same as the latest version endpoint.

Responses: Same as the latest version endpoint (always 202 Accepted).

Example:

curl -X POST "http://localhost:8080/q/flow/exec/examples/order-processing/2.1.0" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${API_KEY}" \
  -d '{"orderId": "12345"}'

POST /q/flow/exec/{namespace}/{name}/sync

Execute the latest version of a workflow synchronously (blocking until completion).

Path parameters:

  • namespace (string, required) - Workflow namespace

  • name (string, required) - Workflow name

Request body (JSON, optional):

{
  "orderId": "12345",
  "customerId": "alice@example.com"
}

Responses:

  • 200 OK - Workflow completed successfully. Response body contains the workflow output directly (structure depends on workflow definition)

  • 400 Bad Request - Invalid input JSON

  • 401 Unauthorized - Authentication required

  • 403 Forbidden - Access denied to namespace

  • 404 Not Found - Workflow not found

Example:

curl -X POST "http://localhost:8080/q/flow/exec/examples/order-processing/sync" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${API_KEY}" \
  -d '{"orderId": "12345", "customerId": "alice@example.com"}'

Example response (200 OK):

{
  "orderId": "12345",
  "status": "fulfilled",
  "trackingNumber": "TRK-789"
}

POST /q/flow/exec/{namespace}/{name}/{version}/sync

Execute a specific version of a workflow synchronously (blocking until completion).

Path parameters:

  • namespace (string, required) - Workflow namespace

  • name (string, required) - Workflow name

  • version (string, required) - Workflow version (semantic version format)

Request body (JSON, optional): Same as the latest version sync endpoint.

Responses: Same as the latest version sync endpoint (200 OK returns workflow output directly).

Example:

curl -X POST "http://localhost:8080/q/flow/exec/examples/order-processing/2.1.0/sync" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${API_KEY}" \
  -d '{"orderId": "12345", "customerId": "alice@example.com"}'

Instance lifecycle (suspend, resume, cancel)

These endpoints control a workflow instance that was started on this Runner, identified by the instanceId returned from an asynchronous execution or listed by the instances endpoints.

Each operation is available in two forms:

  • POST /q/flow/exec/{namespace}/{name}/{version}/{instanceId}/<operation> - targets an instance of a specific workflow version

  • POST /q/flow/exec/{namespace}/{name}/{instanceId}/<operation> - targets an instance of the latest workflow version

The instance is looked up among the active instances of the resolved workflow version. If the instance was started on an older version than the latest one, the "latest" form returns 404 Not Found. Use the versioned form (the workflowVersion field returned by the instances endpoints tells you which one) whenever more than one version is deployed.
Only instances held in this Runner’s in-memory active instance registry can be controlled. Completed, faulted, or cancelled instances, and instances running on another Runner replica, return 404 Not Found.

Path parameters:

  • namespace (string, required) - Workflow namespace (access validated if namespace authorization is enabled)

  • name (string, required) - Workflow name

  • version (string, required in the versioned form) - Workflow version

  • instanceId (string, required) - Workflow instance identifier

Request body: None. Operations requested for the same instance are applied in the order they are received.

Responses (all operations):

  • 200 OK - The operation was applied. The response has no body.

  • 304 Not Modified - The instance is not in a state that allows the operation (see the table below). No change was made.

  • 401 Unauthorized - Authentication required

  • 403 Forbidden - Access denied to namespace

  • 404 Not Found - The workflow version or the active instance was not found on this Runner

HTTP 304 responses do not carry a body, so clients should not expect one. To check the current status of the instance, query GET /q/flow/{namespace}/{name}/{version}/instances.
Operation Effect Returns 304 Not Modified when

suspend

The task currently running finishes, but the next task does not start until the instance is resumed. The instance status becomes SUSPENDED.

The instance is already suspended, or is no longer active.

resume

A suspended instance continues with its next task. The instance status becomes RUNNING.

The instance is not suspended, or is no longer active.

cancel

The Runner tries to cancel the current task. Once it ends, no further task runs and the instance remains CANCELLED permanently. Cancelled instances are removed from the active registry.

The instance is no longer active.

POST /q/flow/exec/{namespace}/{name}[/{version}]/{instanceId}/suspend

Suspend a running workflow instance.

Example:

curl -i -X POST "http://localhost:8080/q/flow/exec/examples/order-processing/2.1.0/550e8400-e29b-41d4-a716-446655440000/suspend" \
  -H "Authorization: Bearer ${API_KEY}"
HTTP/1.1 200 OK

POST /q/flow/exec/{namespace}/{name}[/{version}]/{instanceId}/resume

Resume a suspended workflow instance.

Example:

curl -i -X POST "http://localhost:8080/q/flow/exec/examples/order-processing/2.1.0/550e8400-e29b-41d4-a716-446655440000/resume" \
  -H "Authorization: Bearer ${API_KEY}"

POST /q/flow/exec/{namespace}/{name}[/{version}]/{instanceId}/cancel

Cancel a workflow instance. Cancellation is final: a cancelled instance cannot be resumed.

Example:

curl -i -X POST "http://localhost:8080/q/flow/exec/examples/order-processing/2.1.0/550e8400-e29b-41d4-a716-446655440000/cancel" \
  -H "Authorization: Bearer ${API_KEY}"

End-to-end example

The following sequence starts a long-running workflow, suspends it, inspects it, resumes it, and finally cancels it:

BASE=http://localhost:8080/q/flow
AUTH="Authorization: Bearer ${API_KEY}"

# 1. Start the workflow asynchronously and capture the instance id
INSTANCE_ID=$(curl -s -X POST "$BASE/exec/examples/order-processing/2.1.0" \
  -H "$AUTH" -H "Content-Type: application/json" \
  -d '{"orderId": "12345"}' | jq -r .instanceId)

# 2. Suspend it (200 OK)
curl -i -X POST "$BASE/exec/examples/order-processing/2.1.0/$INSTANCE_ID/suspend" -H "$AUTH"

# 3. Confirm it is suspended
curl -s "$BASE/examples/order-processing/2.1.0/instances?status=SUSPENDED" -H "$AUTH" | jq .

# 4. Resume it (200 OK); resuming again returns 304 Not Modified
curl -i -X POST "$BASE/exec/examples/order-processing/2.1.0/$INSTANCE_ID/resume" -H "$AUTH"

# 5. Cancel it (200 OK); afterwards the instance is no longer active and returns 404
curl -i -X POST "$BASE/exec/examples/order-processing/2.1.0/$INSTANCE_ID/cancel" -H "$AUTH"

Workflow definitions

GET /q/flow/definitions

List all workflow definitions accessible to the authenticated user.

Query parameters:

  • namespace (string, optional) - Filter workflows by namespace

Responses:

  • 200 OK - Array of workflow metadata objects

  • 401 Unauthorized - Authentication required

  • 403 Forbidden - Access denied to the namespace given in the namespace query parameter

Example:

curl -H "Authorization: Bearer ${API_KEY}" \
  "http://localhost:8080/q/flow/definitions?namespace=examples"

Response body example:

[
  {
    "namespace": "examples",
    "name": "hello-runner",
    "version": "1.0.0",
    "dsl": "1.0.0",
    "title": "Simple greeting workflow for the Runner",
    "summary": null,
    "metadata": {},
    "inputSchema": { ... },
    "links": {
      "self": {
        "href": "/q/flow/definitions/examples/hello-runner/1.0.0"
      },
      "execute": {
        "href": "/q/flow/exec/examples/hello-runner/1.0.0"
      }
    }
  }
]

Definition fields:

Field Type Description

namespace, name, version

string

Workflow identity, taken from the document section.

dsl

string

DSL version declared in document.dsl.

title, summary

string or null

Values of document.title and document.summary.

metadata

object

Contents of document.metadata (empty object when not set).

inputSchema

object or null

The workflow’s input.schema, when declared.

links.self.href

string

Path to retrieve the full definition of this version.

links.execute.href

string

Path to execute this version asynchronously.

Without the namespace query parameter, results are automatically filtered to the caller’s authorized namespaces when namespace validation is enabled.

GET /q/flow/definitions/{namespace}/{name}

Get the full workflow definition for the latest version.

Path parameters:

  • namespace (string, required) - Workflow namespace

  • name (string, required) - Workflow name

Request headers:

  • Accept: application/json or Accept: application/yaml (controls response format; JSON is returned when the header is absent or */*)

Responses:

  • 200 OK - Workflow definition in requested format

  • 401 Unauthorized - Authentication required

  • 403 Forbidden - Access denied to namespace

  • 404 Not Found - Workflow not found

  • 415 Unsupported Media Type - Invalid Accept header

Example (YAML format):

curl -H "Accept: application/yaml" \
  -H "Authorization: Bearer ${API_KEY}" \
  http://localhost:8080/q/flow/definitions/examples/hello-runner

GET /q/flow/definitions/{namespace}/{name}/{version}

Get the full workflow definition for a specific version.

Path parameters:

  • namespace (string, required) - Workflow namespace

  • name (string, required) - Workflow name

  • version (string, required) - Workflow version

Request headers and responses: Same as the latest version endpoint.

Workflow instances

These endpoints expose the workflow instances currently tracked in this runner’s in-memory active instance registry. They report only what this specific pod/process is actively holding — not the full execution history persisted to a database. Use them to distinguish instances actively being processed on this runner from instances that are persisted but not running on any pod, for example when building a rebalancer service.

Requires the flow-admin or flow-invoker role.

GET /q/flow/instances

List active in-memory workflow instances across every workflow definition registered on this runner.

Query parameters:

  • status (string, optional) - Filter by workflow status. Only non-terminal values are accepted: PENDING, RUNNING, WAITING, SUSPENDED

Responses:

  • 200 OK - Active in-memory workflow instances on this runner

  • 400 Bad Request - Invalid status filter (terminal statuses COMPLETED, FAULTED, CANCELLED, and unknown values are not allowed)

  • 401 Unauthorized - Authentication required

  • 403 Forbidden - Access denied

Example:

curl -H "Authorization: Bearer ${API_KEY}" \
  "http://localhost:8080/q/flow/instances?status=RUNNING"

Response body example:

{
  "applicationId": "quarkus-flow-runner",
  "instances": [
    {
      "instanceId": "550e8400-e29b-41d4-a716-446655440000",
      "workflowName": "order-processing",
      "workflowNamespace": "examples",
      "workflowVersion": "2.1.0",
      "status": "RUNNING",
      "startedAt": "2026-06-11T10:15:30.123Z"
    }
  ]
}
When namespace authorization is enabled, the returned instances are filtered to the definitions in the user’s authorized namespaces (flow-admin bypasses this filtering).

GET /q/flow/{namespace}/{name}/instances

List active in-memory workflow instances for the latest version of the given namespace/name.

Path parameters:

  • namespace (string, required) - Workflow namespace (access validated if namespace authorization enabled)

  • name (string, required) - Workflow name

Query parameters and responses: Same as GET /q/flow/instances, plus 404 Not Found if the workflow definition does not exist.

GET /q/flow/{namespace}/{name}/{version}/instances

List active in-memory workflow instances for a specific namespace/name/version.

Path parameters:

  • namespace (string, required) - Workflow namespace (access validated if namespace authorization enabled)

  • name (string, required) - Workflow name

  • version (string, required) - Workflow version

Query parameters and responses: Same as the latest version endpoint above.

Example:

curl -H "Authorization: Bearer ${API_KEY}" \
  http://localhost:8080/q/flow/examples/order-processing/2.1.0/instances

Instance fields

Field Type Description

applicationId

string

Identifier of the workflow application (the Runner instance) reporting these instances. Same value as workflowApplicationId in the execution response.

instances

array

List of active instance snapshots (empty array when none match).

instances[].instanceId

string

Unique identifier of the workflow instance.

instances[].workflowName

string

Name of the workflow the instance belongs to.

instances[].workflowNamespace

string

Namespace of the workflow the instance belongs to.

instances[].workflowVersion

string

Version of the workflow the instance belongs to.

instances[].status

string

Current status of the instance. Always one of the non-terminal values PENDING, RUNNING, WAITING, SUSPENDED — terminal instances (COMPLETED, FAULTED, CANCELLED) are removed from the in-memory registry and never appear here.

instances[].startedAt

string (ISO-8601)

Timestamp when the instance started.

This registry is in-memory and per-pod. It does not persist across restarts and, when running multiple Runner replicas, each replica only reports the instances it is currently holding.

Configuration reference

Configuration property fixed at build time - All other configuration properties are overridable at runtime

Configuration property

Type

Default

Enable or disable the file watcher on the source path directory.

When enabled, the runner periodically polls the configured quarkus.flow.runner.source.path directory for new workflow definition files and registers them automatically without requiring a pod restart.

This is a build-time property. Changing it requires a rebuild.

Environment variable: QUARKUS_FLOW_RUNNER_SOURCE_WATCH_ENABLED

boolean

false

Enable or disable the Flow Runner feature.

When false, all runner REST endpoints are disabled and workflow definitions are not loaded from the configured source.

This is the master switch for the entire runner functionality.

Environment variable: QUARKUS_FLOW_RUNNER_ENABLED

boolean

true

Filesystem path to workflow definitions.

Required when type=PATH. The directory is scanned recursively for all .yaml, .yml, and .json files.

In Kubernetes/OpenShift deployments, this typically points to a ConfigMap or persistent volume mount:

quarkus.flow.runner.source.path=/deployments/workflows

Workflows are uniquely identified by namespace:name:version. Duplicate workflow identifiers cause application startup to fail.

Environment variable: QUARKUS_FLOW_RUNNER_SOURCE_PATH

string

Whether to follow symbolic links when scanning for workflow definition files.

When false (default), symbolic links are excluded from the scan. This prevents duplicate workflow detection failures on Kubernetes, where ConfigMap and Secret volume mounts use an internal symlink structure (a timestamped directory with symlinks pointing to the real files).

Set to true only if your workflow files are intentionally provided via symbolic links outside of a Kubernetes volume mount.

Environment variable: QUARKUS_FLOW_RUNNER_SOURCE_FOLLOW_SYMLINKS

boolean

false

Polling interval for detecting new workflow files.

Uses the Quarkus duration format (e.g., "5s", "10s", "1m", or an ISO-8601 duration).

This is a runtime property and can be changed without rebuilding the application.

Environment variable: QUARKUS_FLOW_RUNNER_SOURCE_WATCH_INTERVAL

Duration 

5S

Authentication type.

Selects which authentication mechanism to use:

  • OIDC - Requires quarkus-oidc extension in classpath. Uses standard Quarkus OIDC configuration (quarkus.oidc.*). Roles extracted from JWT claims.

  • API_KEY - Custom filter validates Authorization: Bearer <key> header against configured secrets. Maps keys to roles.

  • NONE - Endpoints are unprotected. Use only in development.

Environment variable: QUARKUS_FLOW_RUNNER_SECURITY_TYPE

oidc, api-key, none

required

JWT claim containing the namespaces authorized for the current identity.

Used when type=OIDC. The claim can contain:

  • A single namespace, for example "team-a"

  • An array of namespaces, for example ["team-a", "team-b"]

  • A comma-separated string, for example "team-a,team-b"

  • The exact value "*" to authorize all current and future namespaces

For a non-admin OIDC identity, a missing, null, empty, or blank claim grants no namespace access when namespace validation is enabled. Identities with the flow-admin role bypass namespace restrictions.

Namespace matching is exact and case-sensitive. Partial wildcard values such as "team-*" are not supported.

Example:

quarkus.flow.runner.security.namespace.claim = namespace

Environment variable: QUARKUS_FLOW_RUNNER_SECURITY_NAMESPACE_CLAIM

string

namespace

Enables or disables namespace authorization.

When enabled:

  • Identities with the flow-admin role can access all namespaces.

  • The exact namespace value "*" authorizes all namespaces.

  • Explicit namespace values authorize only those namespaces.

  • A missing, null, empty, or blank namespace set grants no access to a non-admin identity.

When disabled, namespace authorization is skipped and access is controlled only through role-based authorization.

Environment variable: QUARKUS_FLOW_RUNNER_SECURITY_NAMESPACE_VALIDATE

boolean

true

Enable or disable dynamic workflow operations in OpenAPI document.

When true, the OpenAPI document will include a concrete operation for each registered workflow definition. For example, a workflow test-namespace:hello-world:1.0.0 will generate:

POST /q/flow/exec/test-namespace/hello-world/1.0.0

When false, only the generic parameterized endpoint is documented.

Security Note: The OpenAPI document (/q/openapi) is publicly accessible and will show all registered workflows (namespace, name, version, summary). This serves as a public workflow catalog. Actual execution remains protected by authentication and namespace authorization, returning 401/403 for unauthorized access attempts.

Exposed Information:

  • Workflow namespace, name, and version

  • Workflow summary/description (if configured)

NOT Exposed:

  • Workflow DSL or task definitions

  • Internal business logic or function implementations

  • Secrets, credentials, or sensitive data

Environment variable: QUARKUS_FLOW_RUNNER_OPENAPI_EXPAND_WORKFLOWS

boolean

true

The API key secret value.

Should be loaded from environment variables or Kubernetes Secrets:

quarkus.flow.runner.security.api-keys."my-key".secret=${FLOW_API_KEY}

Environment variable: QUARKUS_FLOW_RUNNER_SECURITY_API_KEYS__API_KEYS__SECRET

string

required

Roles assigned to this API key.

Predefined roles:

  • flow-admin - Full access (definition management + execution)

  • flow-invoker - Execution only (POST/GET /runner/exec/*)

Example:

quarkus.flow.runner.security.api-keys."admin-key".roles=flow-admin
quarkus.flow.runner.security.api-keys."webhook-key".roles=flow-invoker

Environment variable: QUARKUS_FLOW_RUNNER_SECURITY_API_KEYS__API_KEYS__ROLES

list of string

required

Namespaces authorized for this API key.

The value "*" grants access to all namespaces. When omitted, this property defaults to "*" for backward compatibility.

Environment variable: QUARKUS_FLOW_RUNNER_SECURITY_API_KEYS__API_KEYS__NAMESPACES

list of string

*

About the Duration format

To write duration values, use the standard java.time.Duration format. See the Duration#parse() Java API documentation for more information.

You can also use a simplified format, starting with a number:

  • If the value is only a number, it represents time in seconds.

  • If the value is a number followed by ms, it represents time in milliseconds.

In other cases, the simplified format is translated to the java.time.Duration format for parsing:

  • If the value is a number followed by h, m, or s, it is prefixed with PT.

  • If the value is a number followed by d, it is prefixed with P.

Advanced topics

Multiple workflow versions

The Runner supports running multiple versions of the same workflow simultaneously. Workflows are uniquely identified by the namespace:name:version tuple.

Version resolution strategy:

When executing without specifying a version (/q/flow/exec/{namespace}/{name}):

  1. All registered versions of the workflow are retrieved

  2. Versions are sorted using semantic versioning rules (highest first)

  3. The highest version is selected as "latest"

Example:

If you have these versions registered:

  • examples:order-processing:2.1.0

  • examples:order-processing:2.0.0

  • examples:order-processing:1.9.5

Calling /q/flow/exec/examples/order-processing will execute version 2.1.0.

Deployment workflow:

  1. Deploy new workflow version as a separate YAML file:

    workflows/order-processing-v2.1.0.yaml
    document:
      dsl: "1.0.0"
      namespace: examples
      name: order-processing
      version: "2.1.0"
    # ... input, do, and other workflow sections
  2. Update the ConfigMap or volume with the new file

  3. If the file watcher is enabled, the new version is picked up automatically; otherwise restart the Runner

  4. New executions automatically use version 2.1.0

  5. Old executions on version 2.0.0 continue running normally

Version requirements:

  • document.version field is required in all workflow definitions

  • Version must follow semantic versioning format (MAJOR.MINOR.PATCH)

  • Pre-release versions are supported (e.g., 1.0.0-alpha, 2.0.0-rc.1)

  • Invalid version strings cause startup failure with a clear error message

Deploying to Kubernetes/OpenShift

The Runner is designed for cloud-native deployments where workflows are managed as configuration.

ConfigMap-based deployment:

  1. Create a ConfigMap with workflow definitions:

    workflow-definitions.yaml
    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: workflow-definitions
      namespace: workflows
    data:
      hello-runner.yaml: |
        document:
          dsl: "1.0.0"
          namespace: examples
          name: hello-runner
          version: "1.0.0"
          title: "Simple greeting workflow"
        input:
          schema:
            format: json
            document:
              type: object
              properties:
                name:
                  type: string
        do:
          - greet:
              set:
                greeting: "${ \"Hello, \" + .name + \"!\" }"
  2. Mount the ConfigMap in your Deployment:

    deployment.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: workflow-runner
    spec:
      template:
        spec:
          containers:
          - name: runner
            image: quay.io/myorg/workflow-runner:latest
            env:
            - name: FLOW_API_KEY
              valueFrom:
                secretKeyRef:
                  name: flow-secrets
                  key: api-key
            volumeMounts:
            - name: workflows
              mountPath: /deployments/workflows
              readOnly: true
          volumes:
          - name: workflows
            configMap:
              name: workflow-definitions
  3. Configure the Runner to load from the mount path:

    application.properties
    quarkus.flow.runner.enabled=true
    quarkus.flow.runner.source.path=/deployments/workflows
    quarkus.flow.runner.security.type=API_KEY
    quarkus.flow.runner.security.api-keys.default.secret=${FLOW_API_KEY}
    quarkus.flow.runner.security.api-keys.default.roles=flow-admin
    
    # Enable file watcher to pick up new workflows without pod restart
    quarkus.flow.runner.source.watch.enabled=true
    quarkus.flow.runner.source.watch.interval=5s
With the file watcher enabled, adding a new workflow definition to the ConfigMap does not require a pod restart — the Runner detects and registers the new file automatically within the configured polling interval.

GitOps workflow:

  1. Store workflow definitions in a Git repository

  2. Use a GitOps operator (Flux, ArgoCD) to sync workflows to ConfigMaps

  3. With the file watcher enabled, new workflow definitions are picked up automatically — no Rolling Update needed for additions

  4. New versions become available automatically

Troubleshooting

Workflows not loading at startup

Symptom: Application starts but no workflows are registered.

Causes:

  • Path does not exist or is not readable

  • Path contains no valid workflow files

  • Workflow files have syntax errors

Solutions:

  1. Verify the path exists and is readable:

    ls -la /deployments/workflows
  2. Check application logs for parse errors:

    ERROR [io.qua.flo.run.loa] Failed to parse workflow: workflows/broken.yaml
  3. Validate YAML syntax using a linter:

    yamllint workflows/
  4. Ensure required fields are present in document section (dsl, namespace, name, version) and do array is defined

403 Forbidden on execution

Symptom: Execution requests return 403 Forbidden even with valid authentication.

Causes:

  • User not authorized for the workflow’s namespace

  • Namespace claim missing, empty or contains only blank values from JWT token (OIDC mode and non-admin)

  • API key not configured with required namespaces (API_KEY mode)

Solutions:

For OIDC mode:

  1. Inspect the JWT token payload:

    echo $JWT_TOKEN | cut -d'.' -f2 | base64 -d | jq .
  2. Verify the namespace claim contains the requested namespace:

    {
      "namespace": ["examples", "team-a"]
    }
  3. If the claim name is different, configure it:

    quarkus.flow.runner.security.namespace.claim=custom-namespaces

For API_KEY mode:

  1. Check the API key configuration includes the namespace:

    quarkus.flow.runner.security.api-keys.mykey.namespaces=examples,team-a
  2. If empty, the key has access to all namespaces (verify it’s not a typo)

For development, temporarily disable namespace validation:

quarkus.flow.runner.security.namespace.validate=false
Never use this in production.

Version resolution not working

Symptom: Execution fails with 404 Not Found when calling /q/flow/exec/{namespace}/{name} (without version).

Cause: No versions of the workflow are registered with that namespace and name.

Solutions:

  1. List registered workflows:

    curl http://localhost:8080/q/flow/definitions
  2. Verify the document.namespace and document.name match exactly (case-sensitive)

  3. Check that workflow files were loaded at startup:

    INFO  [io.qua.flo.run.loa] Registered workflow: examples:hello-runner:1.0.0
  4. Ensure the workflow definition includes a valid document.version field:

    document:
      dsl: "1.0.0"
      namespace: examples
      name: hello-runner
      version: "1.0.0"  # Required, must be semantic version

Authentication not required (security disabled)

Symptom: Endpoints are accessible without Authorization header.

Cause: Security mode is set to NONE.

Solution:

Verify the security configuration:

quarkus.flow.runner.security.type=API_KEY  # or OIDC

Check application logs for the security mode warning:

WARN  Flow Runner security is disabled (type=NONE)

If the configuration is correct but authentication is still disabled, ensure API keys or OIDC configuration is valid (the Runner may fall back to NONE mode on configuration errors).

What’s next?

Now that you have the Runner operational, explore these advanced topics:

Core Workflow Features:

AI and Integrations:

Production Deployment:

Development and Testing: