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. Use GET /engine/catalog and GET /engine/cluster to fetch catalog and cluster state individually.
GET /engine/catalog
Returns the current catalog state: all registered steps and the engine-level attribute dependency graph.
{
"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": [] }
}
}
GET /engine/catalog/events
Returns all raw events for the catalog aggregate (step registrations, updates, and unregistrations) from sequence zero to the present. Useful for auditing step registration history.
{
"events": [
{
"sequence": 0,
"timestamp": "2026-05-09T17:00:00Z",
"type": "step_registered",
"aggregate_id": ["catalog"],
"data": { "...step_registered payload..." }
}
],
"count": 1
}
GET /engine/cluster
Returns the current cluster state: per-node step health. Configured Raft nodes are always present even if no health events have been received for them.
{
"last_updated": "2026-05-09T17:00:00Z",
"nodes": {
"argyll-1": {
"last_seen": "2026-05-09T17:00:00Z",
"health": {
"lookup-customer": { "status": "healthy" }
}
}
}
}
GET /engine/cluster/events
Returns all raw events for the cluster aggregate (step health changes) from sequence zero to the present.
{
"events": [
{
"sequence": 0,
"timestamp": "2026-05-09T17:00:00Z",
"type": "step_health_changed",
"aggregate_id": ["cluster"],
"data": { "...step_health_changed payload..." }
}
],
"count": 1
}
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": {}
}
}
| Field | Meaning |
|---|---|
goals | Goal Step IDs the Plan was built for |
required | Attribute names that must be supplied in init to satisfy the Plan |
steps | Step definitions snapshotted at Plan creation time |
children | For Sub-Flow Steps, their precomputed child Execution Plans |
attributes | Per-Attribute provider/consumer graph for this Plan |
excluded | Steps 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 unnecessaryblocked: Step → input names whose init values prevent collection from ever satisfying (for example anonepolicy 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.
List and query statuses change only when a Flow is deactivated: a Flow remains active in these results even if its full state already reports terminal completed or failed while work can still produce side effects (changes to external systems) or run compensation. For completed or failed results, timestamp is the deactivation time.
[
{ "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, deactivation-based 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. This response reports terminal completed or failed status as soon as callers can use the outcome; deactivated_at remains unset while pending or active work may still produce side effects, or compensation may still run.
{
"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 deactivation-based status check. This reports active until deactivation; use the full Flow state endpoint or terminal flow events when callers must use the outcome before remaining work has finished.
{
"id": "wf-123",
"status": "completed"
}
GET /engine/flow/{flowId}/events
Returns all raw events for the flow aggregate from sequence zero to the present. Returns 404 if the flow ID is not found.
{
"events": [
{
"sequence": 0,
"timestamp": "2026-05-09T17:00:00Z",
"type": "flow_started",
"aggregate_id": ["flow", "wf-123"],
"data": { "...flow_started payload..." }
},
{
"sequence": 1,
"timestamp": "2026-05-09T17:00:01Z",
"type": "step_started",
"aggregate_id": ["flow", "wf-123"],
"data": { "...step_started payload..." }
}
],
"count": 2
}
Each event has sequence (monotonically increasing within the aggregate), timestamp, type, aggregate_id, and data (the type-specific payload). See the WebSocket reference for the full list of event types.
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.