Skip to main content
The Flywheel MCP server offers 112 tools. A connection only sees the tools its permissions cover (see Permissions). Tools marked destructive either remove data or reach real people; an agent should confirm with the person it works for before calling them. Connected agents can search this catalog with search_docs and kind: "tool".

People, properties and segments

search_people

Permission: people.read · Effect: read-only Search people by any identifier value (email, phone, external id, Stripe id, a full_name identifier): a case-insensitive substring match on identifier values only, so the first_name / last_name fields are not searched. The fastest way to resolve a person when you have something a human typed. Returns up to 50 person summaries, unordered; use get_person for full detail.

list_people

Permission: people.read · Effect: read-only List people with exact filters (subscription status, MRR/LTV ranges, health band, segment membership, identified-only, and what they did: did_event / did_not_event) and pagination. Event conditions query a cohort without saving a segment, e.g. did_event [{event:‘signed_up’, within_days:30}] with did_not_event [{event:‘subscription_started’}]. For free-text lookup use search_people instead.

get_person

Permission: people.read · Effect: read-only Fetch one person in full: names, primary contact details, subscription standing (status, MRR, LTV, payment dates), engagement recency, custom properties, contact methods, and assigned team members.

identify_person

Permission: people.write · Effect: changes data Find or create a person by any identifier (email, phone, external user id, Stripe customer id, anonymous id), through the same pipeline integrations use, including duplicate-contact merging. Idempotent: repeating the call returns the same person. Start here when you are not sure a person exists yet. Resolves by external_user_id first, then stripe_customer_id, email, anonymous_id, phone; at least one is required (a name alone cannot identify anyone). external_user_id must be the org’s real user id for this person, read from their product, never a guess: it is attached as an identifier and never replaces the id the person already has.

delete_person

Permission: people.delete · Effect: changes data, destructive or reaches people Permanently delete one person and everything attached to them: their events, sent-email history, subscriptions, webinar registrations and signals. It cannot be undone. A person with an opt-out or a bounce on record is refused, because their suppression would go with them and they would come back mailable if identified again; it is also refused while tasks or connection invites reference the person. Resolve the person first and confirm with the person you are working for before calling it. Every call is recorded with this connection’s credential.

list_identifiers

Permission: people.read · Effect: read-only List every identifier attached to a person (emails, phones, external ids, Slack ids …) with primary/contact-method flags.

add_identifier

Permission: people.write · Effect: changes data Attach an identifier to a person. Conflict-safe: if the value already belongs to another person the result reports the conflict instead of silently duplicating. Emails are lowercased.

update_identifier

Permission: people.write · Effect: changes data Edit an identifier: its value or its contact-method / primary flags. Promoting to primary demotes the previous primary of that type.

remove_identifier

Permission: people.write · Effect: changes data, destructive or reaches people Delete one identifier from a person. The person remains; only this way of reaching or recognizing them goes.

list_property_configs

Permission: people.read · Effect: read-only The org’s custom property definitions (key, name, type). Values and segment filters both reference these keys — call this before set_person_property or create_segment with custom properties. No parameters.

create_property_config

Permission: people.write · Effect: changes data Define a new custom property (key + display name + type). Do this once per property; then set per-person values with set_person_property.

update_property_config

Permission: people.write · Effect: changes data Rename a custom property or edit its description. Key and type are immutable — existing values and segment filters depend on them.

delete_property_config

Permission: people.write · Effect: changes data, destructive or reaches people Delete a custom property definition AND every per-person value stored under it. Irreversible for the stored values — read it back first if in doubt.

get_person_properties

Permission: people.read · Effect: read-only The custom property values set on one person, keyed by property key.

set_person_property

Permission: people.write · Effect: changes data Set (or overwrite) one custom property value on a person. The key must already have a property config and the value must match its type — create the config first if it doesn’t exist.

delete_person_property

Permission: people.write · Effect: changes data, destructive or reaches people Remove one custom property value from a person entirely (the property config itself stays).

list_segments

Permission: people.read · Effect: read-only Every saved segment (dynamic filtered group of people) with its conditions in public form. System segments are read-only. No parameters.

get_segment

Permission: people.read · Effect: read-only One segment with its filter conditions.

list_segment_members

Permission: people.read · Effect: read-only The people currently matching a segment, newest first, evaluated live by the same engine the dashboard and broadcast sends use. total is the full member count; page with limit/offset. For segment members that also match other filters, use list_people with segment_id.

create_segment

Permission: people.write · Effect: changes data Create a segment from filter conditions (ANDed). Filters reference built-in person fields, custom property keys (call list_property_configs first), or events a person did (“signed_up at least twice in the last 30 days”, “never invoice_paid”). Date and event-window values may be relative tokens (now-30d) that stay rolling. The result shows the conditions as stored; verify membership with list_segment_members.

update_segment

Permission: people.write · Effect: changes data, destructive or reaches people Edit a segment. Passing filters REPLACES the whole condition set — send the complete list you want, not a delta. System segments cannot be edited.

delete_segment

Permission: people.write · Effect: changes data, destructive or reaches people Delete a segment definition. People are untouched — only the saved grouping goes. System segments cannot be deleted.

list_subscriptions

Permission: people.read · Effect: read-only A person’s subscriptions as mirrored from the payment processor: items, cost, status, period and trial dates. Read-only.

get_opt_outs

Permission: people.read · Effect: read-only A person’s marketing opt-out status for email and SMS. No record means fully opted in.

set_opt_outs

Permission: people.write · Effect: changes data Set marketing opt-outs for a person. Opting someone out stops marketing email/SMS to them — honor requests immediately. Opting back IN should only follow explicit consent from the person.

list_data_imports

Permission: people.read · Effect: read-only CSV data-import history, newest first: status, file name, total_rows and processed_rows (successes and failures combined). No parameters.

create_import

Permission: people.write · Effect: changes data Start a people import (contacts from a CSV, spreadsheet or another tool). Parse the file yourself and send its column names plus up to 20 sample rows (objects keyed by column). Creates a draft — nothing is written to people yet — and returns, per column, what the values look like and a suggested target when the header and values agree, plus every valid target (person fields, consent columns, existing properties). Next: set_import_mapping.

set_import_mapping

Permission: people.write · Effect: changes data Decide what every column of a draft import becomes. Every column must be mapped or “skip”. A target is a person field (email, alternate_email, phone, alternate_phone, external_user_id, stripe_customer_id, anonymous_id, first_name, last_name), a consent column (subscribed_to_emails or unsubscribed_from_emails — these only ever opt people OUT), { “property”: “<existing key>” }, or { “new_property”: { “key”: “snake_case”, “name”: ”…”, “type”: “string|number|boolean” } } to declare a new one. At least one identifier is required. Refuses unknown property keys (with the closest match), system keys, two columns on one target, and sample values that do not fit. Returns issues and the first sample rows as the person each becomes. Only saved when there are no errors.

append_import_rows

Permission: people.write · Effect: changes data Add rows to a draft import after its mapping is set, in file order, up to 500 per call (50000 per import). Each row is an object keyed by column. Every row is checked against the mapping; rows that do not fit are rejected with the row number and reason and are not imported. Blank cells are ignored, never written over existing values. Call once per chunk, one call at a time.

preview_import

Permission: people.read · Effect: read-only Dry run of a draft import — writes nothing. Says how many people it will create and update, which existing property values change, how many people get opted out, which new properties get created, and which rows would MERGE two or more existing people into one (a merge deletes all but one and cannot be undone). merges: “block” (default) refuses to produce a token while merges exist; “skip_rows” leaves those rows out; “allow” merges them. Returns a commit_token when ready. Show this preview to the person you are working for before committing.

commit_import

Permission: people.write · Effect: changes data, destructive or reaches people Run a previewed import with its commit_token. Re-checks everything and refuses if anything changed since the preview (re-preview then). Creates the declared new properties, then queues the rows; people are created and updated over the next minutes. Only after the person you are working for has seen preview_import and said go. Follow it with get_import.

get_import

Permission: people.read · Effect: read-only One import’s status, mapping and counts (staged, succeeded, failed), with the reason for each failed row (latest 100).

cancel_import

Permission: people.write · Effect: changes data Delete a draft import and its staged rows. Only drafts: a committed import cannot be cancelled.

events

track_event

Permission: events.ingest · Effect: changes data, destructive or reaches people Record events that people did (1–100 per call), synchronously: each comes back with a status — inserted, duplicate (that event_id is already stored; a safe retry), event_id_conflict (another organization uses that id; pick another), invalid (refused before anything was written; see error) or failed (retry with the same event_id) — plus the stored row id and the person it landed on. An inserted event can carry a warning (stored, but starting its workflows failed): do not re-send it; tell the person you work for. THIS CAN REACH REAL PEOPLE: every live workflow triggered on the event name enrolls the person, even when the timestamp is in the past. Before backfilling history, check which live workflows listen on that name (list_workflows / get_workflow) and confirm with the person you work for. Name each person one way: person_id (resolve with search_people first), or user_id / email / anonymous_id. An unknown person is refused unless create_person: true. track_event never changes a person’s identifiers or properties — use identify_person / set_person_property for that. Always pass a stable event_id so retries cannot double-record. At most 5 event names the org has never recorded may be introduced per call (new_event_names lists them) — a new name creates a new event type, so check spelling against list_events first. Limit: 600 events per minute per organization.

list_events

Permission: events.read · Effect: read-only Read the org’s events, newest first, a page at a time (next_cursor). With person_id: that person’s history, ordered by when each event happened. Without it: the org’s feed, ordered by when Flywheel received each event, and limited to events received in the last 7 days (before until, if given) unless you pass since — the response’s received_since says where that window starts. Filter by event name(s), a time window on when events happened (since / until), or how they arrived (location: fw_api, fw_mcp, fw_pixel, …). The pixel’s autocapture events ($ac_*) are left out unless you name one or pass include_autocapture. Large properties come back cut down (properties_truncated); pass include_properties: false to skip them. Use it to check what names an org records before track_event, to confirm an event landed, or to see what a person did.

Workflows

list_workflows

Permission: workflows.read · Effect: read-only List every workflow (automation) in the organization: id, name, status (live / paused / null for never live), published version. No parameters.

get_workflow

Permission: workflows.read · Effect: read-only Fetch one workflow: definition is the published version (what runs), draft the current draft (what the builder shows and what publish_workflow publishes), both in the database-row form the write tools take, with node labels. has_unpublished_changes says whether the draft would run differently; draft_placeholders lists unfilled “Add step” nodes that block publishing; draft_hash fingerprints the draft for update_workflow_node. Email bodies make this large: view “summary” replaces each body (template_content, template_content_json, html, editor JSON) with { omitted: true, bytes } and keeps subject, preview text, template id and variables — use it unless you are changing a body, and include “draft” or “definition” to get one half. A summary is for reading: never send it back through update_workflow (the write refuses omitted bodies); change single steps with update_workflow_node, or read view “full” before editing bodies.

list_workflow_runs

Permission: workflows.read · Effect: read-only List runs of a workflow, newest first, with total (every run matching the filter, not just this page). status filters by run status; “stuck” lists in_progress runs with no activity for 30 minutes (no log, no step moving, no wait still to end, including a webinar wait whose session moved later), each with last_activity_at and last_node_id (where it stopped) — the ones to look at with get_run and then cancel_run / retry_run.

get_run

Permission: workflows.read · Effect: read-only Fetch one workflow run: its status and attempt, step-by-step logs (each with node_id / node_type, the node’s id as get_workflow and retry_run name it), and steps, the run’s step ledger (which steps were dispatched, are running, are waiting and until when, finished or failed). include_context: false leaves out the run context, which can be tens of KB.

get_node_types

Permission: workflows.read · Effect: read-only The workflow engine’s node-type catalog: every trigger, action, and exit-criteria type with JSON Schemas for its params (per version), the params paths that may hold a {{context.<source>.<path>}} reference (context_ref_fields, per version; a reference anywhere else is refused with FIELD_NOT_EDITABLE), its next wiring shape, and what it contributes to run context. Generated from the same schemas validation enforces. Call this FIRST, before building or editing a workflow definition; never use a type marked executable: false.

get_vocabulary

Permission: workflows.read · Effect: read-only The organization’s referenceable vocabulary: event names, custom property configs, email/SMS sender ids, health score rule ids, webinar ids, team member user ids, and email template ids. Workflow params referencing ids outside this vocabulary fail validation — read it before writing params, never invent ids. Keys: eventNames, customPropertyConfigs (id, name, key), senderConfigIds, smsSenderIds, healthScoreRuleIds, webinarIds, webinarSeriesIds, orgMemberUserIds, templateIds. Pixel autocapture events ($ac_*) are left out of event names to keep the list readable; they are still valid triggers. No parameters.

get_node_context

Permission: workflows.read · Effect: read-only For one node of a saved workflow (its draft first, else its published version): which upstream nodes’ context exists when it runs and the JSON Schema of what each contributes, plus the always-available root context. Use this to pick real {{context.<source_id>.<path>}} references instead of guessing.

validate_workflow

Permission: workflows.read · Effect: read-only Dry-run validation of a workflow definition in database-row form. Runs all three tiers (structure + schemas, context references, org vocabulary) and returns structured findings with stable codes. Never writes. It does NOT run the save-time checks create/update/publish run first (lowercase UUID ids, builder-drawable conditions, trigger_condition must be null), so valid: true can still be refused at save. Pass exit_criteria: [] when there are none. create_workflow / update_workflow return the same findings for what they save, so this is for checking a definition before saving it.

create_workflow

Permission: workflows.write · Effect: changes data Create a workflow as a DRAFT, laid out on the builder canvas so a person can open it. Order of work: get_node_types (params + next shape of each type) → get_vocabulary (ids and event names params may use) → this. The draft saves even when validation finds problems: findings lists them, and errors block publish_workflow, not the save — fix them with update_workflow. saved: false means the graph could not be drawn at all (bad ids, unknown types, broken wiring); nothing was created. Pass automation_id (a UUID you generate) to make retries return the same workflow instead of a duplicate. Nothing runs until publish_workflow and then set_workflow_status live.

update_workflow

Permission: workflows.write · Effect: changes data, destructive or reaches people Rename a workflow and/or REPLACE its draft. definition is the whole graph, not a patch: read it with get_workflow (draft, or definition when there is no draft), change it, send all of it back. Nodes that keep their id keep their labels. A canvas this API laid out that nobody has rearranged is re-laid out on every save. A canvas a person arranged (moved a node or resized an exit criteria box), or one saved before auto layout existed, keeps its positions and new nodes are fitted around them; pass relayout: true once to tidy such a canvas. Returns the saved workflow and the draft’s findings. The published version keeps running unchanged until publish_workflow. If a person has the workflow open in the builder, their next edit there can overwrite a draft saved here — re-read before publishing.

update_workflow_node

Permission: workflows.write · Effect: changes data, destructive or reaches people Change ONE node of the draft in place — its params and/or its canvas name and description — without sending the whole graph back. Positions, wiring, unfilled “Add step” nodes and every other node stay exactly as they are (the one exception: changing a drop_off trigger’s params.config re-draws its branch edges). Pass params (the whole new params object) or params_patch (an RFC 7396 merge patch: objects merge key by key, null deletes a key, arrays and values replace), not both. The write only lands on the draft it read: if the draft changes while the edit is being made (a builder save, another write), nothing is saved and you get failed_precondition — re-read and apply it again. Pass expected_draft_hash (draft_hash from get_workflow) to also refuse when the draft changed since YOUR read. saved: false means the node would not be representable on the canvas — nothing was written and findings say why. Otherwise returns the workflow and the draft’s validation findings; errors block publish_workflow, not the save. The published version keeps running unchanged until publish_workflow. Wiring changes (next, adding or removing nodes) still go through update_workflow.

send_test_email

Permission: workflows.publish · Effect: changes data, destructive or reaches people Send ONE email step of a workflow (send_email / send_marketing_email) as a real email to up to 5 of this organization’s team members — never to anyone else — to check how it reads before publishing. It is rendered exactly as a run would render it: variables are filled from person_id’s data when given (a sample person from search_people), and anything without a value uses the step’s fallback text; fallback_used lists those {{context.*}} paths (with no person, every person variable falls back). The subject is prefixed “[Test] ”. Uses the draft’s version of the step when the draft has it, else the published one (source picks explicitly). No run is created and nothing is recorded as sent, so it never affects who later receives the step. It is a real send through the organization’s sender and counts toward the plan’s email allowance. At most 20 test emails per organization per hour, each recipient counted (resource_exhausted past that).

publish_workflow

Permission: workflows.publish · Effect: changes data, destructive or reaches people Publish the draft — or definition, which is saved as the draft first — as the workflow’s next version. Full validation runs; any error blocks the publish and comes back in findings (published: false). Pass expected_version (the version you read) so you never publish over someone else’s publish. A LIVE workflow runs the new version on its next trigger; a workflow that is not live still needs set_workflow_status live to run at all.

set_workflow_status

Permission: workflows.publish · Effect: changes data Turn a workflow live or pause it. Live means its published version runs on every new trigger — real messages to real people — so confirm with the person you are working for first. Requires a published version and an active subscription. Pausing stops new runs.

delete_workflow

Permission: workflows.write · Effect: changes data, destructive or reaches people Permanently delete a workflow together with its published versions, run history and sent-message history. Cannot be undone. A live workflow is refused — pause it first. To experiment safely, clone_workflow instead.

cancel_run

Permission: workflows.publish · Effect: changes data, destructive or reaches people Stop one run: an in_progress (or failed) run becomes cancelled and runs no further steps. Steps already dispatched and waits already queued are not chased — each checks the run before it runs and stops. Idempotent: an already cancelled run comes back with changed: false, and a completed run is left completed. Pausing a workflow (set_workflow_status) stops NEW runs; this stops one that is under way. A cancelled run can be resumed with retry_run.

retry_run

Permission: workflows.publish · Effect: changes data, destructive or reaches people Re-run a failed or cancelled run from a node, as a new attempt, on the workflow version the run originally ran on (not the current draft or published version). from_action_id defaults to where the run stopped (the node of its last error, or the step a cancel interrupted). Steps from the old attempt still in flight are skipped. Steps run asynchronously: this returns once the first step is dispatched — follow it with get_run. Marketing and transactional emails a person already got from a node are not sent again; SMS, webhooks, Slack and Intercom steps are: if any between from_action_id and the end already ran on this run, the call is refused with the list unless confirm_repeat_side_effects is true. Idempotent: a concurrent second retry dispatches nothing (changed: false). An in_progress run that is stuck must be cancel_run’d first.

clone_workflow

Permission: workflows.write · Effect: changes data Copy a workflow — its draft, or its published canvas when it has no draft — into a new DRAFT workflow. Node ids are regenerated and every reference to them (wiring, exit gates, {{context.<id>}} templates) follows. The copy is never live. Good for trying changes without touching a running workflow.

Webinars

list_webinars

Permission: webinars.read · Effect: read-only List webinar sessions with filters and paging: status, series, a start-time window. For “what is coming up” pass starts_after = now and order = asc. Returns session summaries; use get_webinar for counts of registrants and messages.

get_webinar

Permission: webinars.read · Effect: read-only Fetch one webinar session: its schedule, status, platform link, series, registration counts (registered / attended / no-show / unresolved) and its own messages by status. Use get_webinar_plan for what those messages and bound sequences send and when.

create_webinar

Permission: webinars.write · Effect: changes data Create a one-off webinar session. Pass webinar_id (a v4 UUID you generate) to make it safe to retry: the same id returns the session already made (created: false). For a recurring webinar use create_webinar_series instead. Then link it to its platform (link_webinar_platform) and check what registrants will receive with get_webinar_plan.

update_webinar

Permission: webinars.write · Effect: changes data, destructive or reaches people Change a webinar session. Omitted fields stay as they are; null clears timezone, duration_minutes, join_url or automation_id. A new start_time (a reschedule) or duration_minutes moves every pending reminder, the session’s own messages and the platform attendance pull with it — the response says what moved. status moves: draft → scheduled | canceled; scheduled → live | completed | canceled; live → completed | canceled; canceled → scheduled (restore; messages whose time passed while canceled are not re-sent); completed is final. Canceling stops the session’s own messages and refuses new registrations and attendance; sequence and workflow runs registrants are already in keep going. Linked platforms are not rescheduled — change the time on the platform too. Confirm with the person you work for before canceling or rescheduling a session people registered for.

delete_webinar

Permission: webinars.write · Effect: changes data, destructive or reaches people Delete a webinar session that never gathered anything: no registrations, no messages sent, not part of a series and not a series’ template. Anything else is refused with the reason — cancel it instead (update_webinar status canceled), which keeps the history; for a series session, cancel it or skip its date on the series.

clone_webinar

Permission: webinars.write · Effect: changes data Copy a webinar session to a new start time: title, duration, join link and its messages (as drafts, re-anchored to the new start — nothing sends until scheduled). Registrations and the platform link are not copied; link the clone with link_webinar_platform. Pass new_webinar_id (a v4 UUID) to make a retry safe.

list_webinar_registrants

Permission: webinars.read · Effect: read-only Who registered for a webinar session, with what the session knows about each: attended or not, watch time where the platform reports it, left early, offer clicks, purchase and refund. segment narrows to one audience; segment_counts always covers everyone.

register_for_webinar

Permission: webinars.write · Effect: changes data, destructive or reaches people Register a person for a webinar session. This sends real messages: it enrolls them into every live workflow and sequence bound to the session (confirmations, reminders) and pushes them to the linked platform for their join link. Resolve the person first (search_people, or identify_person to create them). Returns join_url, the link this person joins with: their own when the platform issued one (join_url_personal: true), otherwise the session’s shared link. Idempotent: registering someone twice changes nothing and sends nothing (already_registered: true, with the same join_url). A canceled session refuses registrations. Confirm with the person you work for before registering real people.

set_webinar_attendance

Permission: webinars.write · Effect: changes data, destructive or reaches people Record whether a registered person attended a webinar session (attended: false marks a no-show). This arms the session’s outcome follow-ups — the attended and no-show sequences — so it sends real messages after the session. Linked platforms (Demio, WebinarJam, Zoom) report attendance on their own; use this for sessions without one, or to correct a record.

get_webinar_plan

Permission: webinars.read · Effect: read-only Everything a webinar session sends and when: its own clock-anchored messages, the workflows and sequences bound to it (registration and outcome rails), and — for each entry time — the exact, timestamped list someone registering at that moment receives, with late steps skipped as the runtime skips them. With no entry_times it samples ten days out, three days out, two hours out and after the end. Check this before and after changing a session’s messages or schedule.

get_webinar_revenue

Permission: webinars.read · Effect: read-only What a webinar session earned: registered and attended counts, clicks, buyers, and revenue split into total, attributed to its messages, and unattributed — with a per-message breakdown. reconciles: false means the figures do not add up; report that rather than the numbers. Permission: webinars.write · Effect: changes data Link a webinar session to the platform it runs on, so registrations are pushed there (personal join links) and attendance comes back after it ends. demio: event_id is the Demio event, date_id the session inside it; by default the session’s title, start, duration, timezone and registration link are pulled from Demio (sync_details: false keeps yours) — a moved start re-anchors reminders. webinarjam: event_id is the WebinarJam webinar id, date_id its schedule id — leave it out and the schedule on the session’s date is picked; the operator must also add the registration / attended / missed automation rules on the WebinarJam side. zoom: event_id is the Zoom webinar id (spaces fine); it must exist on the connected Zoom account, and a one-off Zoom webinar can back only one session. Linking a series’ template session to a recurring Zoom webinar (recurring: true) also links every upcoming session of that series to it, and each session the series builds from then on. The response names what was linked (topic) so you can confirm it. Demio and Zoom must be connected in Settings → Integrations; WebinarJam links without an API key but then cannot pick the schedule or push registrations. A recurring WebinarJam webinar shares one schedule id across every date, so it cannot back a series: use one WebinarJam schedule per date. Permission: webinars.write · Effect: changes data, destructive or reaches people Disconnect a webinar session from its platform (Demio, WebinarJam or Zoom): new registrations stop being pushed there and the post-session attendance pull is cancelled. Registrations and attendance already recorded stay. Unlinking a series’ template session from Zoom also unlinks the series’ upcoming sessions that shared that Zoom webinar.

list_webinar_series

Permission: webinars.read · Effect: read-only List webinar series — repeat rules that keep sessions built ahead on a cadence — with cadence, status and template session.

get_webinar_series

Permission: webinars.read · Effect: read-only Fetch one webinar series: cadence, the sessions it has built, skipped dates, and upcoming_unbuilt — the next dates it has not built yet, which is what skip_webinar_occurrence takes.

create_webinar_series

Permission: webinars.write · Effect: changes data Create a recurring webinar: the first session (on the first cadence slot after now) becomes the template, and the series keeps build_ahead_count sessions built ahead, each a copy of the template (title, duration, join link, messages — armed messages stay armed). Set up the template session’s messages, then new sessions inherit them — sessions built during this call copy the template as it is now (empty), so keep build_ahead_count low until the template is ready, or set those sessions’ messages up too. The platform link is inherited only where one link can serve every date: link the template to a recurring Zoom webinar and every upcoming session of the series is linked to it, including the ones already built; with WebinarJam each session is linked to the schedule on its own date; a one-off Zoom webinar or a Demio event stays the template’s alone, so link those sessions one by one. Pass series_id (a v4 UUID) to make a retry safe.

update_webinar_series

Permission: webinars.write · Effect: changes data, destructive or reaches people Change a webinar series’ rule — name, cadence, timezone, build_ahead_count, or status (active builds; paused and archived build nothing). Applies to sessions not built yet; existing sessions keep their times (reschedule one with update_webinar). What each new session copies comes from the template session, so edit that session to change future ones. The series re-checks its horizon immediately.

materialize_webinar_series

Permission: webinars.write · Effect: changes data Build any sessions a webinar series is short of right now, instead of at its next scheduled check. Safe to repeat — it only fills gaps.

skip_webinar_occurrence

Permission: webinars.write · Effect: changes data Stop a webinar series from building one upcoming date (take it from get_webinar_series upcoming_unbuilt). A date that already has a session is not skipped here — cancel that session with update_webinar instead.

unskip_webinar_occurrence

Permission: webinars.write · Effect: changes data Put a skipped date back on a webinar series. If it falls inside the build-ahead horizon its session is built immediately.

get_webinar_series_revenue

Permission: webinars.read · Effect: read-only What a webinar series earned across its sessions, deduplicated (one purchase attributed to two sessions counts once), with a per-session breakdown of the most recent sessions. The per-session figures do not sum to the series total by design.

Broadcasts

list_broadcasts

Permission: broadcasts.read · Effect: read-only List broadcasts, newest first, with filters and paging: status (draft | scheduled | sending | sent | stopped | error), channel (email | sms), webinar_id. Returns summaries with sent / opened / clicked counts; get_broadcast has the body.

get_broadcast

Permission: broadcasts.read · Effect: read-only Fetch one broadcast: subject, preview text and HTML body (or the text), its variables, audience, timing, sender and counts — in the form update_broadcast takes, so edit and send back.

list_broadcast_variables

Permission: broadcasts.read · Effect: read-only What a broadcast’s variables can read: the recipient’s fields, the organization’s custom properties, and — for a broadcast tied to a webinar — the session (title, start time, the recipient’s join link, calendar links). Each entry’s variable goes straight into a variable definition. No parameters.

create_broadcast

Permission: broadcasts.write · Effect: changes data Create a broadcast draft. Nothing is sent: next plan_broadcast, then schedule_broadcast. audience is required: everyone (every person with an email address), segment (with segment_id), or webinar (with webinar_id — the session’s registrants, narrowed by webinar_audience, optionally timed from the session with webinar_offset_minutes). Email needs subject, html and sender_id before it can be scheduled; a text broadcast (channel sms, webinars only) needs message_body. Personalize with variables (list_broadcast_variables). Pass broadcast_id (a v4 UUID you generate) to make the call safe to retry.

update_broadcast

Permission: broadcasts.write · Effect: changes data Change a broadcast draft. Omitted fields stay as they are; null clears preview_text, sender_id or webinar_offset_minutes. html / message_body replace the whole body (read it with get_broadcast first); variables replace the whole variable map — the body and its variables are checked together. Within the same audience kind, stored ids and timing are kept unless you send new ones; switching kind needs the new kind’s id. channel cannot be changed. A scheduled broadcast has to be unscheduled first; a stopped or errored one returns to draft. Any change invalidates an earlier plan — plan again.

delete_broadcast

Permission: broadcasts.write · Effect: changes data, destructive or reaches people Delete a broadcast nobody has received any of (a draft, or a stopped / errored one that never went out). A scheduled one has to be unscheduled first; one that went out stays for its history.

plan_broadcast

Permission: broadcasts.read · Effect: read-only Show exactly what scheduling a broadcast would do, without scheduling it: how many people it reaches (counted the way the sender counts), when it goes out and how long it takes, who it is from, the body as one real recipient would get it, and checks — errors block, warnings are worth a look. Pass send_at (ISO-8601 with an offset, at least an hour from now; plan 90+ minutes out so the plan survives review, since the hour is checked again at scheduling). Omit send_at for a webinar broadcast timed from its session (webinar_offset_minutes set): passing one is an error. When nothing blocks, returns a plan_token for schedule_broadcast (good for an hour, and only while the broadcast stays as planned). Show the plan to the person you work for before scheduling.

schedule_broadcast

Permission: broadcasts.send · Effect: changes data, destructive or reaches people Schedule a broadcast to go out to real people, as planned: pass the plan_token from plan_broadcast. It goes out at the planned time (never sooner than an hour from now) and is refused if anything changed since the plan, the plan is over an hour old, or the send time is now less than an hour away — plan again then. The plan’s checks also run again: anything that now blocks (sender domain unverified, session canceled or started, allowance used up) refuses it, and edits to the target segment’s filters or a rescheduled session count as changes. Only call this after the person you work for has seen the plan and agreed. Until it goes out, unschedule_broadcast (or Unschedule in the dashboard) pulls it back.

unschedule_broadcast

Permission: broadcasts.write · Effect: changes data Take a scheduled broadcast back to draft so nothing goes out. Refused once sending has begun.

get_broadcast_stats

Permission: broadcasts.read · Effect: read-only How a broadcast did: sent / opened / clicked totals and per day (email), and where every queued recipient ended up — sent, still queued, failed, or skipped (and why: opted out, bounced, complained, duplicate, allowance used up, webinar already started).

senders

list_senders

Permission: senders.read · Effect: read-only The email senders a broadcast or workflow email can go out from (use the id as sender_id / sender_config_id) and whether each is ready — a sender needs a verified sending domain. team_member marks a teammate’s own sender (made when they joined) as opposed to an organization sender. Also whether the organization has an SMS number for text broadcasts. No parameters.

list_sending_domains

Permission: senders.read · Effect: read-only The organization’s sending domains — what can follow the @ in a sender’s address. ready = verified, so create_sender can use it; an unverified domain lists the DNS records still missing (adding a domain and verifying it happens in the dashboard, Settings → Email). sender_count is how many senders use each. No parameters.

create_sender

Permission: senders.write · Effect: changes data Create an organization email sender: from_name plus <username>@<domain> on one of the organization’s verified sending domains (list_sending_domains). Nothing is sent — use the returned id as a broadcast’s sender_id or a workflow email’s sender_config_id. Refused for a domain that is not this organization’s or not verified yet. Safe to retry: if a sender already sends from that address it is returned unchanged (created: false; change it with update_sender), and a sender_id you generate makes the call idempotent.

update_sender

Permission: senders.write · Effect: changes data, destructive or reaches people Change a sender. Omitted fields stay as they are; reply_to null = replies go to the from address. A new username or domain_id is checked like create_sender (own verified domain, valid username) and refused if another sender already uses the resulting address. Takes effect at the next send everywhere the sender is used: the result counts the workflows and sequences (live, or with runs still in progress), scheduled broadcasts and published transactional templates that send from it and lists warnings (agents that name the sender are not counted) — tell the person you are working for before changing a sender that is in use. A sender cannot move to another team member or organization.

Signals

list_signal_rules

Permission: signals.read · Effect: read-only List the event rules: each turns a matching event into a signal (a person flagged for action), with its routing (human or agent, rung), dedupe, expiry, resolve_on and follow_up. No parameters.

get_signal_rule

Permission: signals.read · Effect: read-only Fetch one event rule by id.

create_signal_rule

Permission: signals.write · Effect: changes data Create an event rule, or replace one whole when key matches an existing rule (safe to retry). Takes effect within a minute. A rule with handler agent and rung auto starts that agent on every new signal (capped per agent per day); every new signal also emits $fw_signal_created for playbook workflows. To change one field of an existing rule use update_signal_rule.

update_signal_rule

Permission: signals.write · Effect: changes data Change some fields of an event rule. Omitted fields keep their value; null clears any nullable field (follow_up, handler_agent_id, occurrence, key, label, default_assignee_user_id, contact_reason). The merged rule is re-validated whole.

delete_signal_rule

Permission: signals.write · Effect: changes data, destructive or reaches people Delete an event rule. Signals it already wrote keep their history.

list_review_triggers

Permission: signals.read · Effect: read-only List the triggers that summon the AI reviewer to look at a person when an event happens. No parameters.

create_review_trigger

Permission: signals.write · Effect: changes data Summon the AI reviewer when an event (plus filters, optional occurrence count) happens, after a delay, optionally with a focus question.

update_review_trigger

Permission: signals.write · Effect: changes data Change some fields of a review trigger. Omitted fields keep their value.

delete_review_trigger

Permission: signals.write · Effect: changes data, destructive or reaches people Delete a review trigger.

list_signal_actions

Permission: signals.read · Effect: read-only List the AI reviewer’s action catalog: the verbs it may answer with (human_reachout, agent_draft, alert, watch, none, plus custom ones) and how each one’s signals are routed. No parameters.

create_signal_action

Permission: signals.write · Effect: changes data Add a verb to the reviewer’s action catalog. rung_ceiling is fixed at creation and can never be raised.

update_signal_action

Permission: signals.write · Effect: changes data Change how a reviewer action is routed or guided: e.g. route agent_draft to an agent at rung auto. Omitted fields keep their value. rung cannot exceed the action’s rung_ceiling.

delete_signal_action

Permission: signals.write · Effect: changes data, destructive or reaches people Delete a verb from the reviewer’s catalog. Permanent: a deleted shipped verb is not re-seeded.

list_event_dictionary

Permission: signals.read · Effect: read-only What each event means to the AI reviewer: description, salience (critical | notable | routine | noise, which decides what the reviewer sees), category, expected_next, and whether it counts as activity.

update_event_dictionary

Permission: signals.write · Effect: changes data Classify an event the org has already sent, as an operator: salience, category, description, expected_next. Operator edits outrank AI and stats classification. Only events the org has already sent (anything else is not found). Salience critical books a review on every occurrence while the reviewer is on; noise removes the event from the reviewer’s dossier.

get_reviewer_settings

Permission: signals.read · Effect: read-only The AI reviewer’s settings (enabled, scan segment, org brief, debounce, staleness, max findings) and where the org stands against its review allowance this period. No parameters.

update_reviewer_settings

Permission: signals.write · Effect: changes data Change the AI reviewer’s settings. Omitted fields keep their value. scan_segment_id is stored and checked to be one of the org’s segments, but the scan sweep does not apply it yet. Switching the reviewer on needs the AI Reviewer on the plan; check the allowance first, it spends money.

list_signal_agents

Permission: signals.read · Effect: read-only The org’s agents that signals can be routed to, with each one’s daily dispatch cap and how many signals it has been handed today (UTC). No parameters.

list_signal_producers

Permission: signals.read · Effect: read-only The deterministic signal detectors (event_rules, webinar_session_ended) and whether each is active or paused. No parameters.

set_signal_producer_status

Permission: signals.write · Effect: changes data Pause or resume a signal detector. A paused detector writes no signals.

agents

set_signal_agent_cap

Permission: agents.write · Effect: changes data Set how many signals one agent may be handed per UTC day (first dispatches and follow-ups together). Default 25. 0 stops dispatch to it. Signals past the cap wait for the next UTC day rather than being dropped.

Documentation

search_docs

Permission: none (public) · Effect: read-only Search Flywheel’s documentation and this server’s tool catalog. Returns the best-matching doc sections (with the page path to pass to get_doc) and tools (with the permission each needs). Use it before guessing how something works: workflow definition format and node params, validation finding codes, segment filters, broadcast variables and planning, webinar series, signals, connecting and permissions. Ask in plain words, e.g. ‘if/else branch on a custom property’ or ‘why was my broadcast plan refused’.

get_doc

Permission: none (public) · Effect: read-only Read one Flywheel documentation page as markdown — the whole page, or one section of it (a ## section includes its ### subsections). Paths come from search_docs results (e.g. ‘mcp/workflow-definitions’); a full docs URL works too. Start with ‘mcp/overview’ for the map of what the MCP can do and which page covers what.

list_docs

Permission: none (public) · Effect: read-only The table of contents of Flywheel’s documentation: every page’s path, title, one-line description and section headings, grouped as on the docs site. Use it to browse when search_docs does not find what you need; read a page with get_doc.