Skip to main content
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. For connecting a client and how permissions work, see /mcp/overview.

The workflow tools at a glance

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.
1

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:
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.
2

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.
3

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 and Editing a workflow with update_workflow.
4

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.
5

publish_workflow

Publish the draft as the workflow’s next version. Full validation runs again and any error blocks the publish.
6

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.

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:
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. 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.
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.

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:
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.
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.

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.
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.
  • 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.
  • 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.
  • 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):
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.
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.

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