# Workflow API

The Workflow area publishes 41 REST endpoints. Paths are relative to `https://api.lepsto.com`; see [Authentication](/reference/openapi#authentication) for the required header.

### `GET` `/workflow/capability`

List the triggers a workflow can start on and the actions it can call as a step, as declared by the toolkits registered on the platform.

MCP tool: `workflow_capability_list`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| toolkit | query | string | — | Only capabilities of this toolkit, e.g. "mail". |
| kind | query | string one of: trigger, action | — | Only triggers (events, schedule, manual) or only actions (callable tools). |
| query | query | string | — | Case-insensitive substring matched against the key, title and description. |
| cursor | query | string | — | Opaque cursor from a previous page. |
| limit | query | integer | — | Page size, 1-200; defaults to 50. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| items | object[] | • | — |
| next_cursor | string | null | • | — |
| catalog_version | string | • | Version of the snapshot these items came from; record it with a dependency. |

### `GET` `/workflow/capability/get`

Get one capability by key — an action by tool name, or a trigger by event name ("schedule" and "manual" for the built-in triggers).

MCP tool: `workflow_capability_get`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| key | query | string | • | Tool name of an action (e.g. "mail_email_send") or event name of a trigger. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| key | string | • | — |
| http | object | null | — | — |
| kind | string one of: action, event, schedule, batch, manual | • | — |
| title | string | • | — |
| schema | object | — | — |
| toolkit | string | • | — |
| examples | object[] | — | — |
| read_only | boolean | — | — |
| filterable | string[] | — | — |
| composition | object | — | — |
| description | string | — | — |
| schema_hash | string | • | — |
| input_schema | object | — | — |
| output_schema | object | null | — | — |

### `GET` `/workflow/caps`

Every cap key on the Product: what it budgets, how much of the running window is spent, and which workflows share it. A cap key is Product-wide — two workflows naming the same key share ONE budget — so `conflicting` says when they disagree about what that budget is, and `window`/`limit` are null rather than picking between them. Live versions only; a draft is not spending anything yet.

MCP tool: `workflow_cap_list`

**Parameters**

No parameters.

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| items | object[] | • | — |

### `DELETE` `/workflow/config`

Delete a config key of this product.

MCP tool: `workflow_config_delete`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| key | query | string | • | Config key as referenced in a definition: {{ config.<key> }}. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| key | string | • | — |
| deleted | boolean | • | — |

### `GET` `/workflow/config`

List the config keys of this product. Values are never returned — a secret is only ever read by the workflow executor.

MCP tool: `workflow_config_list`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| cursor | query | string | — | Opaque cursor from a previous page. |
| limit | query | integer | — | Page size, 1-200; defaults to 50. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| items | object[] | • | — |
| next_cursor | string | null | • | — |

### `POST` `/workflow/config`

Create or replace a config value for this product. Set `secret: true` for credentials — the value is encrypted at rest and can never be read back. A key marked `deprecated` by workflow_config_list is refused.

MCP tool: `workflow_config_set`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| key | body | string | • | Config key as referenced in a definition: {{ config.<key> }}. |
| value | body | string | • | The value to store. Encrypted at rest when `secret` is true. |
| secret | body | boolean | • | True for credentials: the value is encrypted at rest and never read back. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| key | string | • | — |
| is_secret | boolean | • | — |
| deprecated | boolean one of: true | — | Present when the key still resolves but no longer does anything. Its value is kept; nothing reads it. |
| updated_at | string | • | — |

### `GET` `/workflow/definition`

List the workflows of this product, newest first.

MCP tool: `workflow_definition_list`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| status | query | string one of: draft, enabled, disabled, broken | — | — |
| cursor | query | string | — | Opaque cursor from a previous page. |
| limit | query | integer | — | Page size, 1-200; defaults to 50. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| items | object[] | • | — |
| next_cursor | string | null | • | — |

### `POST` `/workflow/definition`

Create a workflow from a definition. It lands as a draft with version 0; the validator runs and its issues come back, so a draft with issues is still saved — including one with no `trigger` and no nodes yet, which is refused at publish rather than here. The definition's `id`, `product_id`, `name`, `description`, `status`, `version` and `dependencies` are server-owned: send them or not, they are overwritten. `grants`, `config_refs` and `owner` are yours.

MCP tool: `workflow_definition_create`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| name | body | string | • | — |
| description | body | string | — | — |
| definition | body | object | • | A workflow definition: trigger, nodes, edges, grants, config_refs. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| live | object | null | • | — |
| draft | object | null | • | — |
| grants | object | null | • | The workflow principal's grants; null when Governance could not be reached. |
| health | object | null | • | What the dependency check found; null when nothing is unknown. |
| issues | object[] | • | Validation of the draft as it stands right now. |
| timeline | object[] | • | The last 20 health entries, newest first; `workflow_health_list` pages the rest. |
| workflow | object | • | — |
| dependencies | object[] | • | What the live version was validated against, with the hashes it saw. |

### `POST` `/workflow/definition/disable`

Disable a workflow. Runs already in flight finish; no new run starts.

MCP tool: `workflow_definition_disable`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | body | string | • | — |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| id | string | • | — |
| name | string | • | — |
| status | string one of: draft, enabled, disabled, broken | • | `broken` is the dependency check's, not an operator's: a capability is gone. |
| summary | object | • | What starts this workflow and how big it is, without fetching the definition. Taken from the live version when one is live, from the draft otherwise. |
| attention | object | null | • | What is stopping an otherwise published workflow; null when nothing is. |
| created_at | string | • | — |
| updated_at | string | • | — |
| description | string | null | • | — |
| live_version | number | null | • | — |

### `POST` `/workflow/definition/dryrun`

Walk the graph with a sample payload and mocked action outputs, calling nothing. Returns the path taken, every resolved input and a reason for each node not visited.

MCP tool: `workflow_definition_dryrun`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | body | string | — | — |
| definition | body | object | — | A workflow definition: trigger, nodes, edges, grants, config_refs. |
| trigger_payload | body | object | — | What `trigger.data` holds; defaults to a placeholder built from the schema. |
| assumptions | body | object | — | `waits: resolve|timeout`, `split: <branch>` — for node types that execute later. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| path | string[] | • | The node ids this payload would visit, in order. |
| nodes | object | • | — |

### `POST` `/workflow/definition/enable`

Enable a workflow. It needs a published version.

MCP tool: `workflow_definition_enable`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | body | string | • | — |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| id | string | • | — |
| name | string | • | — |
| status | string one of: draft, enabled, disabled, broken | • | `broken` is the dependency check's, not an operator's: a capability is gone. |
| summary | object | • | What starts this workflow and how big it is, without fetching the definition. Taken from the live version when one is live, from the draft otherwise. |
| attention | object | null | • | What is stopping an otherwise published workflow; null when nothing is. |
| created_at | string | • | — |
| updated_at | string | • | — |
| description | string | null | • | — |
| live_version | number | null | • | — |

### `POST` `/workflow/definition/estimate`

Size a batch trigger's audience before enabling it: walks the source tool by cursor, applies the trigger filter and calls nothing else. `skip_if_served_within` is not applied — this is the audience, not one tick. `exact: false` means `count` is a floor.

MCP tool: `workflow_definition_estimate`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | body | string | — | — |
| definition | body | object | — | A workflow definition: trigger, nodes, edges, grants, config_refs. |

**Response**

Returns `object`.

### `GET` `/workflow/definition/export`

The definition of one version as JSON, with any secret-typed literal redacted. Feed it to workflow_definition_import in another product.

MCP tool: `workflow_definition_export`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | query | string | • | — |
| version | query | integer | — | Defaults to the live version. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| version | number | • | — |
| definition | object | • | — |
| workflow_id | string | • | — |

### `GET` `/workflow/definition/get`

One workflow: the draft, a summary of the live version, and the validation issues the draft has right now.

MCP tool: `workflow_definition_get`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | query | string | • | — |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| live | object | null | • | — |
| draft | object | null | • | — |
| grants | object | null | • | The workflow principal's grants; null when Governance could not be reached. |
| health | object | null | • | What the dependency check found; null when nothing is unknown. |
| issues | object[] | • | Validation of the draft as it stands right now. |
| timeline | object[] | • | The last 20 health entries, newest first; `workflow_health_list` pages the rest. |
| workflow | object | • | — |
| dependencies | object[] | • | What the live version was validated against, with the hashes it saw. |

### `POST` `/workflow/definition/import`

Create a draft from an exported definition. `id`, `product_id`, `owner`, `grants` and the version history are dropped: grants are requested again in this product.

MCP tool: `workflow_definition_import`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| name | body | string | • | — |
| definition | body | object | • | A workflow definition: trigger, nodes, edges, grants, config_refs. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| live | object | null | • | — |
| draft | object | null | • | — |
| grants | object | null | • | The workflow principal's grants; null when Governance could not be reached. |
| health | object | null | • | What the dependency check found; null when nothing is unknown. |
| issues | object[] | • | Validation of the draft as it stands right now. |
| timeline | object[] | • | The last 20 health entries, newest first; `workflow_health_list` pages the rest. |
| workflow | object | • | — |
| dependencies | object[] | • | What the live version was validated against, with the hashes it saw. |

### `POST` `/workflow/definition/publish`

Publish the draft as the next immutable version and make it live. Refused with 422 and the issues while validation reports any error. First publish enables the workflow; one an operator disabled stays disabled.

MCP tool: `workflow_definition_publish`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | body | string | • | — |
| note | body | string | • | Why this version exists; shown in the version list. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| grants | object | null | • | What Governance holds for this workflow after the request; null when the request did not reach it. A publish never approves anything — a human sponsors it in Workspace. |
| status | string | • | — |
| version | number | • | — |
| workflow_id | string | • | — |
| dependencies | object[] | • | — |

### `POST` `/workflow/definition/update`

Edit a workflow draft. Published versions are immutable — this only ever writes version 0. The definition's `id`, `product_id`, `name`, `description`, `status`, `version` and `dependencies` are server-owned: send them or not, they are overwritten. `grants`, `config_refs` and `owner` are yours.

MCP tool: `workflow_definition_update`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | body | string | • | — |
| name | body | string | — | — |
| description | body | string | — | — |
| definition | body | object | — | A workflow definition: trigger, nodes, edges, grants, config_refs. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| live | object | null | • | — |
| draft | object | null | • | — |
| grants | object | null | • | The workflow principal's grants; null when Governance could not be reached. |
| health | object | null | • | What the dependency check found; null when nothing is unknown. |
| issues | object[] | • | Validation of the draft as it stands right now. |
| timeline | object[] | • | The last 20 health entries, newest first; `workflow_health_list` pages the rest. |
| workflow | object | • | — |
| dependencies | object[] | • | What the live version was validated against, with the hashes it saw. |

### `POST` `/workflow/definition/validate`

Validate a stored draft or an inline definition. Returns the documented issue codes; publish is refused while any issue has severity "error".

MCP tool: `workflow_definition_validate`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | body | string | — | — |
| definition | body | object | — | A workflow definition: trigger, nodes, edges, grants, config_refs. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| ok | boolean | • | True when no issue has severity "error"; publish needs this. |
| issues | object[] | • | — |

### `GET` `/workflow/grant`

The grants of one workflow's principal: what it may call, what is waiting on a sponsor, and what lost the sponsor it had.

MCP tool: `workflow_grant_list`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | query | string | • | — |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| hint | string | null | • | What to do next, when something needs doing; null when nothing does. |
| grants | object[] | • | — |
| pending | string[] | • | Tool keys asked for and waiting on a human. |
| revoked | string[] | • | Tool keys a human withdrew. Approving one again is not enough: Governance does not resurrect a revoked grant, so it has to be asked for first. |
| effective | string[] | • | Tool keys the workflow may call right now. |
| workflow_id | string | • | — |
| approve_with | string | • | Where a pending grant is approved, and by whom. |
| needs_sponsor | string[] | • | Tool keys that were approved and lost their sponsor. |

### `POST` `/workflow/grant/request`

Ask Governance again for every tool the workflow's live version calls. Use it after a grant was revoked: Governance does not resurrect a revoked grant when a human approves it, so it has to be requested again first — this puts it back to pending, where an Owner or Admin can sponsor it. Idempotent; grants nothing by itself.

MCP tool: `workflow_grant_request`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | body | string | • | — |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| hint | string | null | • | What to do next, when something needs doing; null when nothing does. |
| grants | object[] | • | — |
| pending | string[] | • | Tool keys asked for and waiting on a human. |
| revoked | string[] | • | Tool keys a human withdrew. Approving one again is not enough: Governance does not resurrect a revoked grant, so it has to be asked for first. |
| effective | string[] | • | Tool keys the workflow may call right now. |
| workflow_id | string | • | — |
| approve_with | string | • | Where a pending grant is approved, and by whom. |
| needs_sponsor | string[] | • | Tool keys that were approved and lost their sponsor. |

### `GET` `/workflow/health/dependencies`

The workflows of THIS product whose published versions depend on a toolkit — what a deregistration or a schema change would break. It answers for the calling product only; the cross-product "which Products depend on this toolkit" view is an operator question and is not served here.

MCP tool: `workflow_dependency_list`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| toolkit | query | string | • | The toolkit id a dependency names, e.g. `mail`. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| toolkit | string | • | — |
| workflows | object[] | • | — |

### `GET` `/workflow/health/list`

One workflow's health timeline, newest first: when it broke, what broke it, what fixes it, and when it recovered.

MCP tool: `workflow_health_list`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | query | string | • | — |
| cursor | query | string | — | Opaque cursor from a previous page. |
| limit | query | integer | — | Page size, 1-200; defaults to 50. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| events | object[] | • | Newest first. |
| next_cursor | string | null | • | — |
| workflow_id | string | • | — |

### `GET` `/workflow/health/ping`

Check that the Workflow toolkit is reachable for this product.

MCP tool: `workflow_health_ping`

**Parameters**

No parameters.

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| ok | boolean | • | — |
| toolkit | string | • | — |
| productId | string | • | — |

### `GET` `/workflow/killswitch`

Whether this Product's kill-switch is on, who threw it and when. `forced` means the platform holds it on for everyone.

MCP tool: `workflow_killswitch_get`

**Parameters**

No parameters.

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| at | string | null | • | — |
| by | string | null | • | The identity that last flipped the switch. |
| on | boolean | • | — |
| forced | boolean | • | True when the platform holds it on for every Product (WORKFLOW_KILLSWITCH=1). |

### `POST` `/workflow/killswitch`

Stop or resume every workflow of this Product. Turning it on holds the runs that are running, waiting or queued and expires their waits — nothing is lost, and `workflow_run_replay_all` restarts them afterwards.

MCP tool: `workflow_killswitch_set`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| on | body | boolean | • | True stops this Product's workflows; false lets them run again. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| at | string | null | • | — |
| by | string | null | • | The identity that last flipped the switch. |
| on | boolean | • | — |
| forced | boolean | • | True when the platform holds it on for every Product (WORKFLOW_KILLSWITCH=1). |
| stopped_runs | number | • | Runs this call moved to `held`: everything that was running, waiting or queued. |

### `GET` `/workflow/runs`

Runs of this Product, newest first. Filter by workflow, status or subject — `status=held` is what an agent reads to find what broke, `subject_key` is every run about one person.

MCP tool: `workflow_run_list`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | query | string | — | — |
| status | query | string one of: created, queued, running, waiting, succeeded, failed, held, cancelled, paused_ceiling | — | — |
| test | query | boolean | — | `true` for test runs only, `false` for real traffic only; omitted for both. |
| subject_key | query | string | — | Every run about one subject — the frozen key, not the template. |
| cursor | query | string | — | Opaque cursor from a previous page. |
| limit | query | integer | — | Page size, 1-200; defaults to 50. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| items | object[] | • | — |
| next_cursor | string | null | • | — |

### `POST` `/workflow/runs/cancel`

Stop a run that has not finished and expire its waits. A run that already succeeded, failed or was cancelled is refused.

MCP tool: `workflow_run_cancel`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| run_id | body | string | • | — |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| id | string | • | — |
| test | boolean | • | Started by `workflow_run_test` to watch the workflow work. It executed for real; the flag is what separates it from the Product’s own traffic. |
| status | string one of: created, queued, running, waiting, succeeded, failed, held, cancelled, paused_ceiling | • | — |
| version | number | • | — |
| ended_at | string | null | • | — |
| created_at | string | • | — |
| started_at | string | null | • | — |
| held_reason | string | — | Structured `{ code, tool|name, hint }` while `status` is `held`; null otherwise. |
| subject_key | string | null | • | Who this run is about, rendered once from the definition’s `subject` at run start and frozen. Null when the workflow declares no subject. |
| workflow_id | string | • | — |
| trigger_event_id | string | • | — |
| replay_generation | number | • | — |

### `GET` `/workflow/runs/get`

One run with its step ledger, its append-only event trace and its waits. Step payloads are as stored — redacted per the tool schema (INV-058).

MCP tool: `workflow_run_get`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| run_id | query | string | • | — |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| run | object | • | — |
| steps | object[] | • | — |
| waits | object[] | • | — |
| events | object[] | • | — |

### `POST` `/workflow/runs/replay`

Re-run a held or failed run from the step that stopped it, on the run's own version and over its existing trace. Any other status is refused.

MCP tool: `workflow_run_replay`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| run_id | body | string | • | — |
| reexecute | body | boolean | — | Re-execute the failed step instead of reusing its result: bumps `replay_generation`, which the action's Idempotency-Key carries. Defaults to false. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| id | string | • | — |
| test | boolean | • | Started by `workflow_run_test` to watch the workflow work. It executed for real; the flag is what separates it from the Product’s own traffic. |
| status | string one of: created, queued, running, waiting, succeeded, failed, held, cancelled, paused_ceiling | • | — |
| version | number | • | — |
| ended_at | string | null | • | — |
| created_at | string | • | — |
| started_at | string | null | • | — |
| held_reason | string | — | Structured `{ code, tool|name, hint }` while `status` is `held`; null otherwise. |
| subject_key | string | null | • | Who this run is about, rendered once from the definition’s `subject` at run start and frozen. Null when the workflow declares no subject. |
| workflow_id | string | • | — |
| trigger_event_id | string | • | — |
| replay_generation | number | • | — |

### `POST` `/workflow/runs/replay-all`

Re-run every held or failed run of one workflow, oldest first — what an operator reaches for after turning the kill-switch off or granting a tool. Runs that cannot be replayed are reported with their reason instead of stopping the rest.

MCP tool: `workflow_run_replay_all`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | body | string | • | — |
| status | body | string one of: held, failed | • | Which stopped runs to restart. `held` is what a kill-switch or a missing grant left behind. |
| limit | body | integer | — | How many runs to restart, oldest first; 1-200, defaults to 200. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| refused | object[] | • | — |
| replayed | string[] | • | — |

### `POST` `/workflow/runs/start`

Start a run of an enabled workflow that declares a manual trigger. The idempotency key makes a repeated start return the first one instead of a second run.

MCP tool: `workflow_run_start`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | body | string | • | — |
| idempotency_key | body | string | • | — |
| input | body | object | — | — |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| run_id | string | — | — |
| event_id | string | • | — |
| deduplicated | boolean | • | — |

### `GET` `/workflow/runs/stats`

Run aggregates over a time window: one row per workflow, or — with `workflow_id` — that workflow’s per-node and per-edge counts as well. `waiting` is what is parked right now, not what was parked during the window. `done` is the synthetic end; a workflow whose node is literally named `done` is ambiguous in `edges`.

MCP tool: `workflow_run_stats`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | query | string | — | Omitted for one row per workflow; given for that workflow’s nodes and edges too. |
| window | query | string | — | ISO-8601 duration back from now, `P90D` at most. Defaults to `P7D`. |
| include_test | query | boolean | — | Count `workflow_run_test` runs as well. Defaults to false. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| runs | number | — | — |
| edges | object[] | — | — |
| items | object[] | — | One row per workflow; present only when `workflow_id` was omitted. |
| nodes | object[] | — | — |
| failed | number | — | — |
| window | string | • | — |
| waiting | number | — | — |
| last_run_at | string | null | — | — |
| workflow_id | string | — | — |

### `POST` `/workflow/runs/test`

Start a test run of a workflow’s draft — the live version when there is no draft. It executes for real under the same grants, rate limit and kill-switch as any run, and is flagged `test` so it is not read as the Product’s own traffic. Any trigger kind can be tested: `trigger_payload` becomes `trigger.data` as given, neither checked against a manual trigger’s `input_schema` nor filtered by an event trigger’s conditions — a test run is unconditional.

MCP tool: `workflow_run_test`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | body | string | • | — |
| trigger_payload | body | object | — | What the run reads as `trigger.data`. Defaults to `{}`. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| id | string | • | — |
| test | boolean | • | Started by `workflow_run_test` to watch the workflow work. It executed for real; the flag is what separates it from the Product’s own traffic. |
| status | string one of: created, queued, running, waiting, succeeded, failed, held, cancelled, paused_ceiling | • | — |
| version | number | • | — |
| ended_at | string | null | • | — |
| created_at | string | • | — |
| started_at | string | null | • | — |
| held_reason | string | — | Structured `{ code, tool|name, hint }` while `status` is `held`; null otherwise. |
| subject_key | string | null | • | Who this run is about, rendered once from the definition’s `subject` at run start and frozen. Null when the workflow declares no subject. |
| workflow_id | string | • | — |
| trigger_event_id | string | • | — |
| replay_generation | number | • | — |

### `GET` `/workflow/templates`

The workflow templates, with whether this Product can run each one. A template whose toolkit is not registered here is still listed, marked inapplicable, with what is missing and why — turning that toolkit on is what makes it work. There is no cursor: templates ship in code and there are few of them.

MCP tool: `workflow_template_list`

**Parameters**

No parameters.

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| items | object[] | • | — |

### `POST` `/workflow/templates/apply`

Create a workflow from a template. It lands exactly as `workflow_definition_create` leaves one: a draft at version 0, with the validator’s issues on it — including, on a Product that has not set the config keys the template references, the issue that names `workflow_config_set`. Edit it, then publish it. An inapplicable template applies too: the draft records what it needs rather than being refused with nothing to look at.

MCP tool: `workflow_template_apply`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| template_id | body | string | • | — |
| name | body | string | — | Defaults to the template's own name. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| live | object | null | • | — |
| draft | object | null | • | — |
| grants | object | null | • | The workflow principal's grants; null when Governance could not be reached. |
| health | object | null | • | What the dependency check found; null when nothing is unknown. |
| issues | object[] | • | Validation of the draft as it stands right now. |
| timeline | object[] | • | The last 20 health entries, newest first; `workflow_health_list` pages the rest. |
| workflow | object | • | — |
| dependencies | object[] | • | What the live version was validated against, with the hashes it saw. |

### `GET` `/workflow/version`

The published versions of a workflow, newest first. The draft is not one.

MCP tool: `workflow_version_list`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | query | string | • | — |
| cursor | query | string | — | Opaque cursor from a previous page. |
| limit | query | integer | — | Page size, 1-200; defaults to 50. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| items | object[] | • | — |
| next_cursor | string | null | • | — |

### `GET` `/workflow/version/get`

One version of a workflow, with its stored definition.

MCP tool: `workflow_version_get`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | query | string | • | — |
| version | query | integer | • | 0 is the draft. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| note | string | null | • | — |
| version | number | • | — |
| definition | object | • | — |
| workflow_id | string | • | — |
| dependencies | object[] | null | • | — |
| published_at | string | null | • | — |
| published_by | string | null | • | — |

### `GET` `/workflow/waits`

Approvals and inputs waiting on YOU in this Product — assigned to you by name, to your role, or to your agent principal. Defaults to what is still pending.

MCP tool: `workflow_wait_list`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | query | string | — | — |
| status | query | string one of: pending, resolved, expired | — | Defaults to `pending`. |
| cursor | query | string | — | Opaque cursor from a previous page. |
| limit | query | integer | — | Page size, 1-200; defaults to 50. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| items | object[] | • | — |
| next_cursor | string | null | • | — |

### `GET` `/workflow/waits/all`

Every approval and input of this Product, whoever it was assigned to — the operator’s view of what the Product’s workflows are waiting on. Seeing them all does not resolve them: workflow_wait_resolve still answers only what is assigned to you, or a signed link.

MCP tool: `workflow_wait_list_all`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| workflow_id | query | string | — | — |
| status | query | string one of: pending, resolved, expired | — | Defaults to `pending`. |
| cursor | query | string | — | Opaque cursor from a previous page. |
| limit | query | integer | — | Page size, 1-200; defaults to 50. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| items | object[] | • | — |
| next_cursor | string | null | • | — |

### `GET` `/workflow/waits/get`

One wait: which workflow and run it belongs to, what it asks, when it gives up, and — for an `input` — the form its answer is checked against.

MCP tool: `workflow_wait_get`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| wait_id | query | string | • | — |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| id | string | • | — |
| form | string | — | The JSON Schema an `input` answer is checked against; null for an approval. |
| kind | string | • | `approval` or `input`. |
| run_id | string | • | — |
| status | string | • | — |
| message | string | null | • | What the node asks, as the author wrote it. |
| node_id | string | • | `node_id`, or `node_id[i]/child_id` inside a loop. |
| assignee | object | null | • | Who was asked, as the definition wrote it. Null when the row predates the shape. |
| deadline | string | null | • | — |
| created_at | string | • | — |
| resolved_by | string | null | • | — |
| workflow_id | string | • | — |
| workflow_name | string | null | • | — |

### `POST` `/workflow/waits/resolve`

Answer an approval (`decision`) or an input (`payload`, checked against the node’s form) and let the run continue. Authorised for the assignee, or for the holder of the signed `link_token` the notification carried — not for whoever merely holds write. A wait that was already answered or has expired is refused.

MCP tool: `workflow_wait_resolve`

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| wait_id | body | string | • | — |
| decision | body | string one of: approve, reject | — | Required for an `approval`. |
| comment | body | string | — | — |
| payload | body | object | — | Required for an `input`: the answer, checked against the node's form. |
| link_token | body | string | — | The signed token from the notification link. Resolves on behalf of the addressee. |

**Response**

Returns `object`.

| Field | Type | Always | Description |
| --- | --- | --- | --- |
| wait | object | • | — |
| channel | string one of: tool, link | • | How the resolution arrived; recorded on the run. |
| resolved_by | string | • | Who the run’s trace records as having answered. |
