Skip to main content
Flow agents are Sapot Chat’s AI conversation agents. Over MCP you work with them at the intent level — describe what a flow should do in natural language and the server-side Designer builds the graph, read a flow back as a human-language outline, run scripted test conversations, attach inboxes, and publish. The Flow Designer graph itself (graph_data, graph_snapshots, designer_history) never crosses the wire: reads are stripped server-side, builds accept only natural language, and there is no export, import, or restore tool.
Four scopes gate this domain: flows:manage for reads + inbox wiring, flows:design for building and modifying flows by intent, flows:test for scripted test runs, and flows:publish for going live. A key granted flows:design but not flows:publish can build and refine a draft but cannot take it live. See Authentication & Scopes and Safety & Boundaries.

The intent-level view

Every flow-agent read returns the same agent shape — never the graph:
You will never receive the flow’s internal graph. Don’t ask for or expect raw graph JSON — it’s IP, stripped at the API boundary. To understand a flow, read get_flow_outline; to change it, describe the change with modify_flow (or open the Flow Designer in the dashboard).

Building blocks

When you build with create_flow / modify_flow, describe the flow in these terms — they are also how get_flow_outline and test transcripts read a flow back:
  • Message — sends text (and/or media) to the customer, then continues. Greetings, recommendations, confirmations.
  • Collect — asks a question and stores the answer in a named field (a “slot”), e.g. email, phone_number. It re-asks if the answer is unclear. Say what to ask and what to store it as.
  • Condition / branch — routes the conversation down different paths based on what the customer said or a value already collected. Describe each branch and where it should lead.
  • Knowledge lookup — answers a customer question from the account’s AI Knowledge base instead of a fixed script.
  • Repeating series — loops to collect a list of items (e.g. several order lines) before moving on.
  • Handoff / End — finishes the flow: hand the conversation to a human, or end with a final message.
  • Tools — a step can fire an account tool: a calendar tool offers real available slots and books appointments (see Calendar Tools); a product tool recommends items from the catalogue. The designer sometimes wires these in when your instruction clearly names the need — treat wiring as yours to confirm with list_flow_capabilities + attach_flow_capability (below).

flows:manage

list_flow_agents

List the account’s flow agents. Takes no arguments. Returns a list of agents in the intent-level shape above. The underlying flow graph is intentionally not included.

get_flow_agent

Fetch one flow agent by id (intent-level view, no graph). Returns the same fields as list_flow_agents, for one agent.

get_flow_outline

Read a human-language outline of what a flow does, step by step. Returns the flow’s name, description, status, step_count, branch_count, and an ordered steps list. Each step has a stable per-read ref (n1, n2, …), a friendly kind/action (e.g. “sends a message”, “asks a question and stores the answer”, “checks a condition”, “hands off to a human”), the user-facing copy (text), where a collected answer is stored (stores_as), and its routing (goes_to, or branches with when/go_to for conditions).
The refs are how you point at a step everywhere else: “insert a step after n3” in a modify_flow instruction, or step_ref="n4" in attach_flow_capability. Refs are stable within one read — re-read the outline after a change before using them again.

get_flow_agent_metrics

Runtime metrics across the account’s flow agents — conversations handled, hand-offs, and so on. Returns the aggregated metrics object.

set_flow_agent_inboxes

Set which inboxes a flow agent is connected to. This REPLACES the current set — it is not additive. Returns the updated agent (intent-level view).
inbox_ids is the full desired set, and an inbox belongs to exactly one flow agent. Adding an inbox here moves it off whatever other agent it was on. To add one inbox without dropping the rest, read the current inbox_ids first and send the union. Sending [] detaches every inbox from this agent.
Per-inbox inbox_settings. Keyed by inbox id as a string, each value carries the runtime rules for that inbox:

flows:design — build & modify by intent

An agent describes a flow in plain language; the graph is generated and stored server-side. Builds run in the background: create_flow / modify_flow return a run id you poll with get_flow_run.

create_flow

Create a new flow agent from a natural-language instruction. Returns {id, flow_agent_id, status: "queued"}. Poll get_flow_run(id) until it resolves. The new flow starts as a draft; publish_flow_agent when you and the user are happy with it.
If a run comes back failed or needs_input, do not call create_flow again — that leaves an orphan blank draft. Retry with modify_flow(flow_agent_id, …) using the flow_agent_id you already have, and send a full, self-contained instruction (each run is stateless, so a bare answer to a clarification loses context).

modify_flow

Modify an existing flow agent by natural-language instruction. Its current flow is the starting point. Returns {id, flow_agent_id, status: "queued"}; poll get_flow_run(id) exactly as for create_flow.

get_flow_run

Poll a build/modify run. Polling is free — it never counts against your action quota. Returns {id, flow_agent_id, status, created_at} plus, by status: The raw graph is never included in any state. Also readable with flows:test.

list_flow_capabilities

List the account tools you can attach to a flow’s steps. Returns {capabilities: […]}, one per attachable tool, each with name (the key you pass to attach_flow_capability), tool_type (calendar or product), kind (act for calendar — books/offers real slots; answer for product — composed reply), description, and actions (calendar → ["offer","book"], product → ["recommend"]). Empty if the account has no tools — create a calendar tool or add product catalogue entries first.

attach_flow_capability

Wire a tool into one step of a flow, pointing at the step’s outline ref. The designer builds messages/questions/conditions but does not always attach calendar or product tools — confirm and fix the wiring here. The graph is edited server-side by ref — no raw node id is sent or returned. Returns the updated outline.
For a full booking flow, attach offer and book to two different steps: one that presents the available slots, and a later one that books the customer’s pick.

flows:test — scripted test runs

test_flow

Run a scripted customer conversation through a flow. Testing never touches live traffic, and works on drafts and published flows alike. Returns {id, flow_agent_id, status: "queued"}; the run executes in the background — poll get_flow_test_run(id).

get_flow_test_run

Poll a test run and read back the whole conversation + outcome. Polling is free — it never counts against your action quota. On completed returns:
  • transcript — the full conversation as an ordered list; each turn has your user message, the flow’s agent reply, and the status after that turn;
  • outcome — the final status (active / handed_off / completed), collected (every field the flow captured, e.g. {"full_name": "…", "phone_number": "…"}), and turns (count).
On failed returns a generic error. The raw graph is never included.

Branch a test run

A completed run can be continued from one of its turns with only the new messages, so trying a different answer at step 5 no longer re-runs steps 1 to 4. Send the same request as a new run to POST /api/v1/accounts/{account_id}/flow_test_runs with branch_from:
The 202 carries branched_from and branch_turn, plus warnings: ["flow_changed"] when the flow was saved after the parent run — the step you continue from may have moved. On completed, the transcript starts with the parent’s rows up to that turn, each marked "copied": true, then the new turns numbered after them; outcome.collected holds the fields kept from the parent plus the new ones, and outcome.turns counts every row. The parent run is never changed, and a branch can itself be branched. Refused with 422 and an error_code: branch_parent — the run is not a completed run of this flow in this account; branch_turn — the turn is outside the run, ended the flow, or belongs to a run recorded before branching existed (run it again, then branch). The duplicate-test guard remembers the kept messages plus the new ones as one script but counts only the new turns toward its pace limits, and a chain of branches never grows a run past 20 turns. The test_flow tool does not take these fields yet: branch through the endpoint with an agent token that holds flows:test, and poll the result with get_flow_test_run as usual.

Know when a run is still valid

Every run records the flow version it ran against and says whether the flow changed since, so you can read a run back instead of paying for it again:
  • flow_version on the 202 and on every poll — the flow’s last save plus the account’s AI Knowledge. flow_changed: false means nothing that shapes the answers has changed since the run.
  • Sending the exact same messages (and scenario) to the same flow version again, with an agent or personal token, answers 409 with error_code: "repeat_of", the earlier run_id and its status: nothing is queued or billed, and the pointer comes before any credit check. A completed run is there to read back with get_flow_test_run; a queued or processing one is still running, so poll it instead of sending the script again. A run that failed, or a flow saved since, lets the script run again, and a repeated branch (same parent run, turn and messages) is pointed at the earlier branch the same way. People testing from the dashboard are not pointed; they keep the pace rules.
  • Runs are kept for 30 days with their sandbox rows, then swept.

flows:publish

publish_flow_agent

Publish a flow agent so it goes live on its connected inboxes. The graph is validated server-side first. If it’s invalid, the call fails and the error is opaque — it tells you the graph didn’t pass, not which node is broken (the graph itself stays private). On success, returns the published agent (intent-level view).
If publish reports the graph is invalid, iterate from the MCP side with get_flow_outline + modify_flow (describe what to fix), or open the agent in the dashboard’s Flow Designer, resolve the validation errors there, then call publish_flow_agent again.

Typical flow — build, test, go live

1

Build by intent

create_flow(instruction="…") → poll get_flow_run(id) until completed — the poll returns the built flow’s outline.
2

Wire in tools

list_flow_capabilities(flow_agent_id) to see what’s attachable, then attach_flow_capability(flow_agent_id, step_ref, capability) on the step that should fire it.
3

Test it

test_flow(flow_agent_id, messages=[…]) → poll get_flow_test_run(id) → read the transcript and outcome.collected. Refine with modify_flow and re-test until it behaves. To try another answer at one step, branch the run from that turn (see Branch a test run) instead of re-sending the whole script; before re-running a script at all, check flow_changed on your last run (see Know when a run is still valid).
4

Attach inboxes

set_flow_agent_inboxes(flow_agent_id, inbox_ids=[…]) with the full desired inbox set (resolve ids with list_inboxes), plus any per-inbox inbox_settings.
5

Go live

publish_flow_agent(flow_agent_id) (needs flows:publish).

What’s deliberately absent

There is no export, import, restore_snapshot, or raw-graph tool for flow agents, and no way to supply or receive graph_data through any tool. Building and editing happen only through natural-language instructions compiled server-side; reading happens only through the IP-free outline. This isn’t a gap in the tool catalog — it’s a locked product boundary. See Safety & Boundaries for the full rationale.

Connect