Engine API

Every REST endpoint the engine exposes, with request and response shapes: register/update/delete Steps, start and query Flows, preview execution plans, check step health, and post async webhook completions.

The engine listens on port 8080 by default. All paths below assume http://localhost:8080.

Engine

GET /engine

Returns the combined catalog and cluster snapshot.

{
  "last_updated": "2026-05-09T17:00:00Z",
  "steps": {
    "lookup-customer": { "...full step definition..." }
  },
  "attributes": {
    "customer_id": {
      "providers": [],
      "consumers": ["lookup-customer"]
    },
    "customer": {
      "providers": ["lookup-customer"],
      "consumers": []
    }
  },
  "health": {
    "argyll-1": {
      "last_seen": "2026-05-09T17:00:00Z",
      "health": {
        "lookup-customer": { "status": "healthy" }
      }
    }
  }
}

The attributes map is the engine-level dependency graph: each attribute name lists which Steps produce it and which consume it. The health map is keyed by node ID; each node has last_seen and a per-step health map. HealthState.status is one of healthy, unhealthy, or unknown, with an optional error field for unhealthy states.

GET /health

Liveness check, returns 200. Response includes X-Argyll-Raft-State header (leader, candidate, follower, or unknown) for leader-aware load balancers.

Steps

POST /engine/step

Register a Step.

{
  "id": "lookup-customer",
  "name": "Lookup Customer",
  "type": "sync",
  "http": {
    "method": "GET",
    "endpoint": "https://api.example.com/customers/{customer_id}",
    "timeout": 5000
  },
  "attributes": {
    "customer_id": { "role": "required", "type": "string" },
    "customer": { "role": "output", "type": "object" }
  }
}

GET /engine/step

List all registered steps.

{
  "steps": [
    { "id": "lookup-customer", "name": "Lookup Customer", "...": "..." },
    { "id": "send-confirmation", "name": "Send Confirmation", "...": "..." }
  ],
  "count": 2
}

GET /engine/step/{stepId}

Get a specific Step definition. Returns the full Step registration object.

PUT /engine/step/{stepId}

Replace a Step definition with a new version. Body shape matches POST /engine/step.

DELETE /engine/step/{stepId}

Unregister a Step.

Step Health

GET /engine/health

Aggregated Step health across the cluster (a ClusterState).

{
  "last_updated": "2026-05-09T17:00:00Z",
  "nodes": {
    "argyll-1": {
      "last_seen": "2026-05-09T17:00:00Z",
      "health": {
        "lookup-customer": { "status": "healthy" },
        "send-confirmation": { "status": "unhealthy", "error": "connection refused" }
      }
    }
  }
}

GET /engine/health/{stepId}

Resolved health for one Step merged across nodes.

{ "status": "healthy" }

Returns 404 if no health record exists for the Step.

Flows

POST /engine/flow

Start a Flow.

{
  "id": "wf-123",
  "goals": ["send-confirmation"],
  "init": {
    "customer_id": ["cust-456"],
    "order_amount": [99.99]
  },
  "labels": { "customer": "cust-456" }
}

init values are arrays per Attribute, this disambiguates scalar vs. array values. labels is optional.

Response:

{
  "message": "",
  "flow_id": "wf-123"
}

The Flow runs asynchronously. Poll GET /engine/flow/{flowId} or subscribe via WebSocket to track progress.

POST /engine/plan

Preview an Execution Plan without starting a Flow. Same request body as starting a Flow (the id field is ignored).

{
  "goals": ["send-confirmation"],
  "init": {
    "customer_id": ["cust-456"]
  }
}

Response is the full ExecutionPlan:

{
  "goals": ["send-confirmation"],
  "required": ["customer_id", "order_amount"],
  "steps": {
    "lookup-customer": { "...": "..." },
    "validate-payment": { "...": "..." },
    "send-confirmation": { "...": "..." }
  },
  "attributes": {
    "customer_id": { "providers": [], "consumers": ["lookup-customer"] },
    "customer":    { "providers": ["lookup-customer"], "consumers": ["validate-payment", "send-confirmation"] },
    "valid":       { "providers": ["validate-payment"], "consumers": ["send-confirmation"] }
  },
  "children": {},
  "excluded": {
    "satisfied": {},
    "blocked": {},
    "missing": {}
  }
}
FieldMeaning
goalsGoal Step IDs the Plan was built for
requiredAttribute names that must be supplied in init to satisfy the Plan
stepsStep definitions snapshotted at Plan creation time
childrenFor Sub-Flow Steps, their precomputed child Execution Plans
attributesPer-Attribute provider/consumer graph for this Plan
excludedSteps reachable from the Goals that were excluded, with the reason

The excluded block has three sub-maps:

  • satisfied: Step → output names already in init, so the Step is unnecessary
  • blocked: Step → input names whose init values prevent collection from ever satisfying (for example a none policy with an init value present)
  • missing: Step → required input names that could not be satisfied by any provider or init value

GET /engine/flow

List recent flows. Active flows appear first, then completed and failed flows, all sorted most-recent first within each group. Each item includes id, status, and timestamp.

[
  { "id": "wf-123", "status": "completed", "timestamp": "2026-05-09T17:00:02Z" },
  { "id": "wf-122", "status": "failed",    "timestamp": "2026-05-09T16:55:00Z" }
]

POST /engine/flow/query

Query flows with ID prefix, labels, status filters, and pagination.

Request:

{
  "id_prefix": "wf-",
  "labels": { "customer": "cust-456" },
  "statuses": ["active", "failed"],
  "limit": 50,
  "cursor": "",
  "sort": "recent_desc"
}

All fields are optional. sort accepts recent_desc (most-recent first) or recent_asc (oldest first, default for query). Note: GET /engine/flow always returns recent_desc.

Response:

{
  "flows": [
    {
      "id": "wf-123",
      "status": "active",
      "timestamp": "2026-05-09T17:00:00Z"
    }
  ],
  "count": 1,
  "total": 120,
  "has_more": true,
  "next_cursor": "opaque-cursor-string"
}

Pass next_cursor back as cursor to fetch the next page.

GET /engine/flow/{flowId}

Get full Flow state.

{
  "id": "wf-123",
  "status": "completed",
  "created_at": "2026-05-09T17:00:00Z",
  "completed_at": "2026-05-09T17:00:02Z",
  "deactivated_at": "2026-05-09T17:00:02Z",
  "last_updated": "2026-05-09T17:00:02Z",
  "plan": { "...ExecutionPlan as above..." },
  "labels": { "customer": "cust-456" },
  "metadata": { "source": "checkout" },
  "attributes": {
    "customer_id": [
      { "value": "cust-456", "set_at": "2026-05-09T17:00:00Z" }
    ],
    "customer": [
      {
        "value": { "id": "cust-456", "name": "Alice" },
        "step": "lookup-customer",
        "set_at": "2026-05-09T17:00:01Z"
      }
    ]
  },
  "executions": {
    "lookup-customer": {
      "status": "completed",
      "started_at": "2026-05-09T17:00:00Z",
      "completed_at": "2026-05-09T17:00:01Z",
      "duration": 1000,
      "inputs": { "customer_id": "cust-456" },
      "outputs": { "customer": { "id": "cust-456", "name": "Alice" } }
    }
  }
}

Each entry in attributes is an array of AttributeValue objects (value, step, set_at). Multiple values can accumulate for the same Attribute name when several upstream Steps produce it; the consumer’s collect policy decides how they’re combined. step is omitted for values from Flow init.

executions maps Step ID to its execution record (status, started_at, completed_at, duration, inputs, outputs, error, plus work_items for Steps with for_each). Failed and skipped steps also include unsatisfied: an array of input names that could not be satisfied.

GET /engine/flow/{flowId}/status

Lightweight status check.

{
  "id": "wf-123",
  "status": "completed"
}

Webhook

POST /webhook/{flowId}/{stepId}/{token}

Async Step completion. POST output Attributes on success or application/problem+json on failure. The engine deduplicates by token. Duplicate completions for the same token are ignored and return 200, so retrying a webhook call is safe.

WebSocket

GET /engine/ws

Real-time event stream. See WebSocket API.