> ## Documentation Index
> Fetch the complete documentation index at: https://flywheel.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Workflows

> Build, publish, turn live, clone, delete and debug Flywheel workflows through the MCP server: the build loop, draft vs published vs live, validation findings and run history.

A workflow reacts to something a person does (an event, a drop-off, a health score change, a webinar registration or attendance) and runs a graph of steps for that person: waits, branches, emails, texts, property updates and so on. Through the Flywheel MCP server at `https://api.flywheel.cx/mcp` an agent can build workflows, publish them, turn them live and read every run.

This page covers how to operate workflows. The definition format itself (node rows, ids, `next` wiring, params, context references, conditions, every validation finding code, and complete worked examples) is on [/mcp/workflow-definitions](/mcp/workflow-definitions). For connecting a client and how permissions work, see [/mcp/overview](/mcp/overview).

## The workflow tools at a glance

| Tool | Permission | What it does |
| - | - | - |
| `list_workflows` | `workflows.read` | Every workflow in the org: id, name, status, published version. |
| `get_workflow` | `workflows.read` | One workflow: its published `definition`, its `draft`, and whether they differ. |
| `get_node_types` | `workflows.read` | The node-type catalog: every trigger, action and exit-criteria type with the JSON Schema of its params, its `next` shape and what it adds to run context. |
| `get_vocabulary` | `workflows.read` | The org's event names and the ids params may reference (senders, custom properties, webinars, team members, templates). |
| `get_node_context` | `workflows.read` | For one node of a saved workflow, which `{{context.*}}` references it can use. |
| `validate_workflow` | `workflows.read` | Dry-run validation of a definition. Never writes. |
| `list_workflow_runs` | `workflows.read` | Recent runs of a workflow, newest first. |
| `get_run` | `workflows.read` | One run with its full context and step-by-step logs. |
| `create_workflow` | `workflows.write` | Create a workflow as a draft. |
| `update_workflow` | `workflows.write` | Rename a workflow and/or replace its draft. |
| `clone_workflow` | `workflows.write` | Copy a workflow into a new draft workflow. |
| `delete_workflow` | `workflows.write` | Permanently delete a workflow and its history. |
| `publish_workflow` | `workflows.publish` | Publish the draft as the next version. |
| `set_workflow_status` | `workflows.publish` | Turn a workflow `live` or `paused`. |

A credential only sees and can call the tools its permissions allow. An API key that has `workflows.read` and `workflows.write` but not `workflows.publish` can build and save drafts but can never publish or turn anything live; a person has to do that.

Every workflow id (`automation_id`) and run id (`run_id`) is a UUID read from another tool's output. Never invent one. The only ids you generate yourself are new node ids inside a definition and, optionally, the `automation_id` of a workflow you are creating.

## The build loop

Building or changing a workflow follows the same order every time. Skipping discovery is the main way agents produce definitions with made-up node types, params or ids.

<Steps>
  <Step title="get_node_types">
    Read the catalog for the node types you plan to use. Each entry gives `type`, `kind` (`trigger`, `action` or `exit_criteria`), `executable`, `versions` (each with a `params_schema`), `next_family` and `next_schema` (how the node is wired), and `context_schema` (what the node contributes to run context). Pass `types` to fetch only what you need:

    ```json theme={null}
    { "types": ["new_event", "wait", "if_else", "send_marketing_email"] }
    ```

    Never use a type whose `executable` is `false`. Today that is `send_agent_marketing_email`: it is agent-only, the workflow runtime cannot execute it and the builder cannot draw it, so a save that contains it is refused with `NOT_EXECUTABLE`.
  </Step>

  <Step title="get_vocabulary">
    Read the org's vocabulary. It takes no arguments and returns `eventNames`, `customPropertyConfigs` (`id`, `name`, `key`), `senderConfigIds`, `smsSenderIds`, `healthScoreRuleIds`, `webinarIds`, `webinarSeriesIds`, `orgMemberUserIds` and `templateIds`. Every event name and id you put into params should come from here. Pixel autocapture events (names starting `$ac_`) are left out of `eventNames` to keep the list readable, but they are still valid triggers.
  </Step>

  <Step title="create_workflow or update_workflow">
    Send the definition. `create_workflow` makes a new workflow; `update_workflow` replaces the draft of an existing one. Both save a draft and return validation `findings` for what they saved. See [Creating a workflow with create\_workflow](#creating-a-workflow-with-create_workflow) and [Editing a workflow with update\_workflow](#editing-a-workflow-with-update_workflow).
  </Step>

  <Step title="Fix what the findings say">
    Each finding has a stable `code`, a `severity` (`error` or `warning`), the `node_id` and `param_path` it is about, a human `message`, and sometimes `expected` (valid values). Errors block publishing; warnings do not. Fix the errors, send the whole definition again with `update_workflow`, and repeat until no errors remain. Every code and its fix is listed on [/mcp/workflow-definitions](/mcp/workflow-definitions#validation-finding-codes).
  </Step>

  <Step title="publish_workflow">
    Publish the draft as the workflow's next version. Full validation runs again and any error blocks the publish.
  </Step>

  <Step title="set_workflow_status live">
    Turn the workflow on. Only now does it run for real people, so confirm with the person you are working for first.
  </Step>
</Steps>

## Draft, published and live

A workflow has three independent states, stored the same way the dashboard's workflow builder stores them, so a person and an agent can hand a workflow back and forth.

* **Draft.** What the builder shows when someone opens the workflow, and what `publish_workflow` publishes. `create_workflow`, `update_workflow` and `clone_workflow` only ever write the draft. A draft can be incomplete or invalid; it is saved anyway and its problems come back as findings.
* **Published.** A numbered version (`version` 1, 2, 3, and so on; 0 means never published). Only `publish_workflow` creates one. `get_workflow` returns it as `definition`.
* **Live.** The workflow's `status`: `live`, `paused`, or `null` for a workflow that has never been live. Only `set_workflow_status` changes it. A live workflow runs its published version on every new trigger.

`get_workflow` shows all three:

```json theme={null}
{ "automation_id": "6f1c2a9e-3b7d-4e8f-a1c2-9d0e4b5f6a7c" }
```

The response's `workflow` has `id`, `name`, `status`, `version`, `published` (true once any version exists), `definition` (the published version), `draft` (the current draft, or `null` when there is none), `has_unpublished_changes` (true when the draft would run differently from the published version) and `draft_placeholders` (unfilled "Add step" nodes a person left in the builder; publishing is refused while any remain).

Editing a live workflow is safe in the sense that nothing changes for real people until you publish: the published version keeps running unchanged while you work on the draft. But the moment you call `publish_workflow` on a live workflow, its next trigger runs the new version.

## What reaches real people, and when to confirm

Nothing a workflow does reaches a real person until the workflow is both published and live.

| Call | Reaches real people? |
| - | - |
| `create_workflow`, `update_workflow`, `clone_workflow`, `validate_workflow` | No. Drafts never run. |
| `publish_workflow` on a workflow that is not live | No. It still needs `set_workflow_status` `live` to run at all. |
| `publish_workflow` on a workflow that is already live | Yes. The new version runs on the next trigger. |
| `set_workflow_status` with `live` | Yes. The published version runs on every new trigger from now on. |
| `set_workflow_status` with `paused` | Stops new runs. |
| `delete_workflow` | Destroys data: the workflow, its versions, run history and sent-message history. |

Confirm with the person you are working for before:

* calling `set_workflow_status` with `live`,
* calling `publish_workflow` on a workflow whose `status` is `live`,
* calling `delete_workflow`.

When you ask, say what the workflow will do in plain terms: which trigger starts it, what it sends, to whom and after how long. `get_workflow` gives you everything you need to describe it.

## Creating a workflow with create\_workflow

`create_workflow` creates a new workflow as a draft, laid out on the builder canvas so a person can open it straight away.

Arguments:

* `name` (required): the name shown in the dashboard. Trimmed; must be non-empty and at most 200 characters.
* `definition` (optional): the workflow in database-row form, `{ triggers, actions, exit_criteria }`. Omit it for an empty draft, which opens in the builder exactly like a workflow made with "New workflow".
* `automation_id` (optional): a lowercase UUID you generate. Retrying a create with the same `automation_id` returns the workflow it already made instead of creating a duplicate, so pass one whenever a retry is possible. An id already used by anything else is refused.

```json theme={null}
{
	"name": "Welcome series",
	"automation_id": "6f1c2a9e-3b7d-4e8f-a1c2-9d0e4b5f6a7c",
	"definition": {
		"triggers": [ "..." ],
		"actions": [ "..." ],
		"exit_criteria": []
	}
}
```

The response has `saved`, `created`, `workflow` and `findings`:

* `saved: true, created: true`: the workflow was made. `findings` lists every validation problem in the draft. The draft saves even when there are errors; errors block `publish_workflow`, not the save.
* `saved: true, created: false`: an idempotent replay. A workflow with this `automation_id` already existed in your org and is returned as it is. `findings` is empty on a replay, so call `validate_workflow` or re-save with `update_workflow` if you need them.
* `saved: false`: the graph could not be represented on the builder canvas at all, so nothing was created and `workflow` is `null`. `findings` says why: ids that are not lowercase UUIDs, duplicate ids, unknown or non-executable node types, a `next` that does not match the node type, a target that is not an action, an exit gate that does not exist, or a condition shape the builder cannot draw. Fix those and call `create_workflow` again.

Complete definitions you can adapt are on [/mcp/workflow-definitions](/mcp/workflow-definitions#worked-example-event-wait-email).

## Editing a workflow with update\_workflow

`update_workflow` renames a workflow, replaces its draft, or both.

Arguments:

* `automation_id` (required).
* `name` (optional): a new name.
* `definition` (optional): the whole graph. It **replaces** the draft; it is not a patch. Any node you leave out is removed from the draft.
* `relayout` (optional): `true` lays the canvas out afresh. Without it, nodes that keep their id keep their canvas position and labels, so your edit does not scramble a layout a person arranged.

Passing none of `name`, `definition` or `relayout: true` is refused.

The safe way to edit:

1. Call `get_workflow`.
2. Take `draft`, or `definition` when `draft` is `null`.
3. Change what you need, keeping every existing node's id.
4. Send all of it back:

```json theme={null}
{
	"automation_id": "6f1c2a9e-3b7d-4e8f-a1c2-9d0e4b5f6a7c",
	"definition": {
		"triggers": [ "...every trigger, changed or not..." ],
		"actions": [ "...every action, changed or not..." ],
		"exit_criteria": [ "...every exit criteria..." ]
	}
}
```

The response has `saved`, `workflow` and `findings`, with the same meaning as for `create_workflow`: `saved: false` means the definition could not be drawn and the draft was left untouched.

`relayout: true` without a `definition` tidies the existing draft in place. It is refused when the draft has no nodes, or when it still has unfilled "Add step" placeholders.

<Warning>
  If a person has the workflow open in the builder while you save, their next edit there can overwrite the draft you saved. Re-read with `get_workflow` before you publish, and check the draft is still yours.
</Warning>

## Publishing with publish\_workflow

`publish_workflow` publishes the draft as the workflow's next version.

Arguments:

* `automation_id` (required).
* `definition` (optional): publish this instead of the saved draft. It is saved as the draft first, then published.
* `expected_version` (optional): the `version` you last read from `get_workflow`. If the workflow has been published since, the call is refused with a message telling you to re-read and retry, so you never publish over someone else's publish.

```json theme={null}
{
	"automation_id": "6f1c2a9e-3b7d-4e8f-a1c2-9d0e4b5f6a7c",
	"expected_version": 2
}
```

The response has `published`, `version` and `findings`:

* `published: true`: `version` is the new version number. `findings` may still contain warnings.
* `published: false`: at least one finding is an error. Nothing was published and `version` is unchanged. Fix the errors with `update_workflow` and publish again.

Publishing is refused outright (an error, not findings) when there is no draft and no `definition`, and when the draft still contains unfilled "Add step" placeholders (listed in `get_workflow`'s `draft_placeholders`). To clear placeholders, either remove them by sending a full `definition` through `update_workflow`, or pass `definition` to `publish_workflow` directly.

A workflow that is not live still needs `set_workflow_status` `live` after publishing. A workflow that is already live runs the new version on its next trigger.

## Turning a workflow live or pausing it with set\_workflow\_status

`set_workflow_status` turns a workflow `live` or `paused`.

```json theme={null}
{ "automation_id": "6f1c2a9e-3b7d-4e8f-a1c2-9d0e4b5f6a7c", "status": "live" }
```

* `live` requires a published version. A workflow that has never been published is refused with a message telling you to call `publish_workflow` first.
* `live` also requires the organization's subscription to be in good standing. With no subscription it is refused ("Add a payment method in Billing"); with a subscription that is `past_due`, `canceled`, `incomplete`, `unpaid` or `incomplete_expired` it is refused naming that status. These are for a person to resolve in Billing; an agent cannot.
* `paused` stops new runs.

The response is the workflow summary: `id`, `name`, `status`, `version`, `created_at`, `published`.

Live means real messages to real people on every new trigger. Confirm with the person you are working for before you call it with `live`.

## Cloning a workflow with clone\_workflow

`clone_workflow` copies a workflow into a brand-new draft workflow. It copies the source's draft, or its published canvas when there is no draft.

```json theme={null}
{
	"automation_id": "6f1c2a9e-3b7d-4e8f-a1c2-9d0e4b5f6a7c",
	"name": "Welcome series (experiment)",
	"new_automation_id": "0b8e7d6c-5a4f-4e3d-9c2b-1a0f9e8d7c6b"
}
```

* `name` defaults to `Copy of <source name>`.
* `new_automation_id` is optional; pass a lowercase UUID you generate to make retries return the same copy.
* Every node id is regenerated, and every reference to those ids follows: `next` wiring, `exit_gate_id`, and `{{context.<node_id>...}}` references inside params.
* The copy is never live and has no published version.
* The response is the same shape as `create_workflow` (`saved`, `created`, `workflow`, `findings`).

Clone when you want to try changes without touching a workflow that is running: edit the copy with `update_workflow`, and only publish it (or carry the changes back to the original) once it is right.

## Deleting a workflow with delete\_workflow

`delete_workflow` permanently deletes a workflow together with its published versions, its run history and its sent-message history. It cannot be undone.

```json theme={null}
{ "automation_id": "6f1c2a9e-3b7d-4e8f-a1c2-9d0e4b5f6a7c" }
```

* A live workflow is refused. Pause it first with `set_workflow_status` `paused`, so no run is cut off mid-flight, then delete it.
* The response is `{ "deleted": true }`.

Confirm with the person you are working for before deleting. If the goal is only to stop a workflow, `set_workflow_status` `paused` does that and keeps the history.

## Validating without saving with validate\_workflow

`validate_workflow` runs the publish-time validation on a definition and returns `valid` and `findings`. It never writes anything. Its arguments are the three row arrays at the top level (all three are required, so pass `exit_criteria: []` when there are none):

```json theme={null}
{
	"triggers": [ "..." ],
	"actions": [ "..." ],
	"exit_criteria": []
}
```

`create_workflow` and `update_workflow` already return the same findings for what they save, so `validate_workflow` is mainly for checking a definition before you commit it to a draft.

<Warning>
  `validate_workflow` does not run the save-time representability checks that `create_workflow`, `update_workflow` and `publish_workflow` run first. A definition can come back `valid: true` from `validate_workflow` and still be refused at save with `saved: false`, for example when node ids are not lowercase UUIDs (`INVALID_NODE_ID`) or a `new_event` trigger has a non-null `trigger_condition` (`CONDITION_NOT_EDITABLE`, only a warning in `validate_workflow`). Treat the save's response as the final word.
</Warning>

## Debugging with list\_workflow\_runs and get\_run

Each time a live workflow triggers for a person, it creates a run. Use the run tools to see what actually happened.

`list_workflow_runs` lists a workflow's runs, newest first:

```json theme={null}
{ "automation_id": "6f1c2a9e-3b7d-4e8f-a1c2-9d0e4b5f6a7c", "limit": 20, "offset": 0 }
```

`limit` is 1 to 200 (default 50) and `offset` defaults to 0. Each run has `id`, `run_number`, `status` (`in_progress`, `completed` or `failed`), `automation_id`, `automation_version` (which published version ran), `triggered_by`, `created_at` and `completion_time`. For an event trigger, `triggered_by` reads `event_id:<event id>|trigger_id:<trigger node id>`.

`get_run` returns one run in full:

```json theme={null}
{ "run_id": "a3b4c5d6-e7f8-4a9b-8c0d-1e2f3a4b5c6d" }
```

On top of the summary fields it has:

* `context`: the run's context. `root_context` holds the trigger kind and the person (`org_user`); every node that has run adds its contribution under its own node id. This is exactly what `{{context.<source_id>.<path>}}` references resolve against.
* `logs`: one entry per step, oldest first, with `action_id`, `status` (`success`, `error`, `in_progress` or `skipped`), `message`, `triggered_at`, `completed_at`, `details` and `warnings`.

A debugging routine:

1. `list_workflow_runs` and pick a run whose `status` is `failed`, or one for the person in question.
2. Check `automation_version`. If it is older than the workflow's current `version`, the run used an earlier published version and your latest change was not part of it.
3. `get_run` and read `logs` in order. The first `error` entry's `message` and `details` usually name the cause.
4. For a send step, read that step's contribution in `context` (under the step's `action_id`). Email steps record a `status` (`success`, `failure` or `skipped`) and, when it did not send, a `code` such as `user_marketing_email_opt_out`, `user_blacklisted_email_bounced`, `user_blacklisted_email_complained`, `duplicate_email` or `email_limit_reached`.
5. If a message rendered with blank values, look in `context` for the path your `{{context.*}}` reference points at. A reference that does not resolve at run time becomes `null` silently, which is why validation checks references at save time.
6. To check that a workflow is wired the way you think, read it with `get_workflow` and use `get_node_context` on the node in question.

No runs at all usually means the workflow is not live (`status` is not `live`), it was never published, or no event matching the trigger has arrived since it went live.

## Common mistakes

* **Inventing ids.** Workflow ids, run ids, sender ids, webinar ids, custom property ids and team member ids all come from list/get tools and `get_vocabulary`. Only node ids are yours to generate, and they must be fresh lowercase v4 UUIDs.
* **Reusing or shortening node ids.** Ids like `trigger-1` or `wait_a` are refused with `INVALID_NODE_ID`. Ids must be unique across triggers, actions and exit criteria.
* **Sending a partial definition to update\_workflow.** `definition` replaces the whole draft. Read the draft with `get_workflow`, change it, and send every node back.
* **Editing the published `definition` while a draft exists.** When `get_workflow` returns a non-null `draft`, that is what `publish_workflow` publishes. Build on the draft, not on `definition`, or you discard someone's unpublished work.
* **Treating a saved draft as done.** `saved: true` with error findings is a draft that cannot publish. Read `findings` after every save.
* **Assuming publish turns the workflow on.** It does not. A workflow that is not live needs `set_workflow_status` `live`.
* **Turning a workflow live without asking.** Live sends real messages to real people. Confirm first.
* **Using a non-executable node type.** Never use a type whose `executable` is `false` in `get_node_types` (`send_agent_marketing_email`).
* **Guessing context paths.** Use `get_node_context` on a saved workflow to see which `{{context.*}}` references a node can use, instead of guessing and round-tripping through `UNKNOWN_CONTEXT_PATH` errors.
* **Putting a condition on the trigger.** `trigger_condition` on a `new_event` trigger must be `null`. Put the check in an `if_else` or `filter` step right after the trigger.
* **Trusting validate\_workflow alone.** It skips the save-time checks. The response from `create_workflow`, `update_workflow` or `publish_workflow` is authoritative.
* **Deleting to stop.** Pausing stops new runs and keeps the history. Deleting destroys it.

## Related pages

* [/mcp/workflow-definitions](/mcp/workflow-definitions): the definition format, params, context references, conditions, every finding code and worked examples.
* [/mcp/people](/mcp/people): find the people a workflow runs for, and manage the custom properties workflows branch on.
* [/mcp/broadcasts](/mcp/broadcasts): one-off emails to an audience, as opposed to workflows that react to triggers.
* [/mcp/webinars](/mcp/webinars): webinar sessions and series, whose registrations and attendance can trigger workflows.
* [/mcp/signals](/mcp/signals): signal playbooks are workflows on the `$fw_signal_created` event.
* [/mcp/overview](/mcp/overview): connecting a client and permissions.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.