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:Building blocks
When you build withcreate_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).
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).
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.
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.
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 yourusermessage, the flow’sagentreply, and thestatusafter that turn;outcome— the finalstatus(active/handed_off/completed),collected(every field the flow captured, e.g.{"full_name": "…", "phone_number": "…"}), andturns(count).
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 toPOST /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_versionon the 202 and on every poll — the flow’s last save plus the account’s AI Knowledge.flow_changed: falsemeans nothing that shapes the answers has changed since the run.- Sending the exact same
messages(andscenario) to the same flow version again, with an agent or personal token, answers 409 witherror_code: "repeat_of", the earlierrun_idand itsstatus: nothing is queued or billed, and the pointer comes before any credit check. Acompletedrun is there to read back withget_flow_test_run; aqueuedorprocessingone 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).
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 noexport, 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.

