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 Never use a type whose
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: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_workflowpublishes.create_workflow,update_workflowandclone_workflowonly 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 (
version1, 2, 3, and so on; 0 means never published). Onlypublish_workflowcreates one.get_workflowreturns it asdefinition. - Live. The workflow’s
status:live,paused, ornullfor a workflow that has never been live. Onlyset_workflow_statuschanges it. A live workflow runs its published version on every new trigger.
get_workflow shows all three:
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_statuswithlive, - calling
publish_workflowon a workflow whosestatusislive, - calling
delete_workflow.
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 sameautomation_idreturns 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.
saved, created, workflow and findings:
saved: true, created: true: the workflow was made.findingslists every validation problem in the draft. The draft saves even when there are errors; errors blockpublish_workflow, not the save.saved: true, created: false: an idempotent replay. A workflow with thisautomation_idalready existed in your org and is returned as it is.findingsis empty on a replay, so callvalidate_workflowor re-save withupdate_workflowif you need them.saved: false: the graph could not be represented on the builder canvas at all, so nothing was created andworkflowisnull.findingssays why: ids that are not lowercase UUIDs, duplicate ids, unknown or non-executable node types, anextthat 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 callcreate_workflowagain.
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):truelays 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.
name, definition or relayout: true is refused.
The safe way to edit:
- Call
get_workflow. - Take
draft, ordefinitionwhendraftisnull. - Change what you need, keeping every existing node’s id.
- Send all of it back:
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.
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): theversionyou last read fromget_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.
published, version and findings:
published: true:versionis the new version number.findingsmay still contain warnings.published: false: at least one finding is an error. Nothing was published andversionis unchanged. Fix the errors withupdate_workflowand publish again.
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.
liverequires a published version. A workflow that has never been published is refused with a message telling you to callpublish_workflowfirst.livealso 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 ispast_due,canceled,incomplete,unpaidorincomplete_expiredit is refused naming that status. These are for a person to resolve in Billing; an agent cannot.pausedstops new runs.
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.
namedefaults toCopy of <source name>.new_automation_idis 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:
nextwiring,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).
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_statuspaused, so no run is cut off mid-flight, then delete it. - The response is
{ "deleted": true }.
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.
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:
context: the run’s context.root_contextholds 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, withaction_id,status(success,error,in_progressorskipped),message,triggered_at,completed_at,detailsandwarnings.
list_workflow_runsand pick a run whosestatusisfailed, or one for the person in question.- Check
automation_version. If it is older than the workflow’s currentversion, the run used an earlier published version and your latest change was not part of it. get_runand readlogsin order. The firsterrorentry’smessageanddetailsusually name the cause.- For a send step, read that step’s contribution in
context(under the step’saction_id). Email steps record astatus(success,failureorskipped) and, when it did not send, acodesuch asuser_marketing_email_opt_out,user_blacklisted_email_bounced,user_blacklisted_email_complained,duplicate_emailoremail_limit_reached. - If a message rendered with blank values, look in
contextfor the path your{{context.*}}reference points at. A reference that does not resolve at run time becomesnullsilently, which is why validation checks references at save time. - To check that a workflow is wired the way you think, read it with
get_workflowand useget_node_contexton the node in question.
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-1orwait_aare refused withINVALID_NODE_ID. Ids must be unique across triggers, actions and exit criteria. - Sending a partial definition to update_workflow.
definitionreplaces the whole draft. Read the draft withget_workflow, change it, and send every node back. - Editing the published
definitionwhile a draft exists. Whenget_workflowreturns a non-nulldraft, that is whatpublish_workflowpublishes. Build on the draft, not ondefinition, or you discard someone’s unpublished work. - Treating a saved draft as done.
saved: truewith error findings is a draft that cannot publish. Readfindingsafter every save. - Assuming publish turns the workflow on. It does not. A workflow that is not live needs
set_workflow_statuslive. - 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
executableisfalseinget_node_types(send_agent_marketing_email). - Guessing context paths. Use
get_node_contexton a saved workflow to see which{{context.*}}references a node can use, instead of guessing and round-tripping throughUNKNOWN_CONTEXT_PATHerrors. - Putting a condition on the trigger.
trigger_conditionon anew_eventtrigger must benull. Put the check in anif_elseorfilterstep right after the trigger. - Trusting validate_workflow alone. It skips the save-time checks. The response from
create_workflow,update_workfloworpublish_workflowis authoritative. - Deleting to stop. Pausing stops new runs and keeps the history. Deleting destroys it.
Related pages
- /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_createdevent. - /mcp/overview: connecting a client and permissions.