flow, so you
create, edit, version and call it with the same API key and the same routes.
Base URL https://sandbox.voice.miraiminds.co. All endpoints require
Authorization: Bearer sk_live_….
When to use a flow
A flow can run tools before, during, and after a call. Each start
or conversation node lists the on-call tools it offers in
data.tools. The
model sees those tools alongside the node’s outgoing transitions. For a full
editor example, follow the Node.js cookbook.
How a flow runs
- The call opens on the start node. Its
greetingis spoken exactly as written, with{{variables}}filled in. With no greeting, the model opens the call from the start node’s prompt. - The model follows the current node. Its instructions are the global
node’s prompt (when the node’s
add_global_promptistrue), then the current node’s prompt. - Transitions are functions. Each transition out of the current node is
offered to the model as a function with no arguments:
labelis its name andconditionis its description. When the model decides the condition is met, it calls the function and the call moves to the target node. - The conversation carries across nodes. Moving to a node replaces the instructions and keeps the history, so the model still knows everything said so far. It then replies as the new node.
- Speech plays before the move. If the model speaks and calls a transition in the same reply, the move happens after that speech has played.
- An end node ends the call. The agent speaks its reply to the end node’s
prompt, then hangs up. The call ends
completedwithended_reason: "assistant-ended-call". - Extraction runs as the call leaves a node. A node with
extraction_enabledhas itsextraction_variablesextracted from the conversation in the background when the call moves on. The node the call ends on is extracted as the call ends.
- Barge-in. While the agent speaks, meaningful speech from the caller cuts
it off. Backchannels such as “हाँ”, “जी” or “ok” do not. On a node with
allow_interrupt: falsethe agent finishes its line, then answers what the caller said. Once a goodbye is playing, nothing the caller says reopens the call. - Silence. After about 10 seconds without the caller speaking, the agent
checks in once. If the caller stays silent, the agent says a short goodbye
and the call ends with
ended_reason: "idle-timed-out". A call created withtiming.user_silence_close_secsuses that timer instead.
Generate a flow from a brief
Describe the agent in plain language and the API drafts the graph for you. Start here, then read and adjust the draft with the schema below. If you already have recordings of good calls, you can also draft the flow from them.
Generating a flow saves nothing. The response is a draft for you to review, test
and save. Each generation is charged to your wallet at
what the model’s provider reports it cost, plus 20%.
200 OK
Choose a model
Start with the default. Try another model when a draft misses branches or rules
your brief asked for. A draft from two example calls costs more and takes
longer: about ₹8 to ₹12, and up to a minute on Claude Opus 5.5. With any model,
set your client’s timeout to a few minutes. The examples below use 5.
What a draft costs
Each generation is charged to your wallet at what the model’s provider reports it cost, plus 20%. The response’scost says what was
charged. If the provider reports no cost for an attempt, we charge that
attempt’s tokens at the chosen model’s published rates per million input and
output tokens. You can find those rates on
Billing & tiers.
- Every attempt counts. We add up the cost of every attempt and round the total up to the paisa once.
- Failures are charged too. A
422 flow_generation_failed, and a503after the model had already answered, are charged for what the model did. Both responses carryid,model,usageandcost. Nothing is charged when the model never answered. - The wallet must hold a positive balance. If it is empty, the request is
refused with
402 insufficient_balancebefore the model runs. A charge never takes the wallet below zero. - Where it shows. Each charge is one wallet transaction of kind
flow_generation, and the billing statement lists the charges as AI flow drafts.
Generate a draft
- cURL
- Python
- Node.js
Save it on an agent
Create the agent with the draft’sflow and its input_schema. Save them
together: when an agent has an input_schema, every variable a node reads
must be declared in it.
- cURL
- Python
- Node.js
input_schema, each call must send every declared variable in
variables, and nothing else: a missing or undeclared variable is refused with
400 invalid_request. Send number and boolean variables as JSON numbers
and booleans. Then test the agent in the browser
before you dial anyone.
Revise a flow
To change a flow, send the current graph asflow and describe only the
change in brief. The draft keeps what the brief does not ask to change,
including node IDs, transition labels and the editor layout. Save the result
with PATCH, together with its input_schema (the change may read a new
variable), and if_revision so you never overwrite someone else’s edit.
- cURL
- Python
- Node.js
Generation errors
422 uses the same shape as invalid_flow, plus the charge:
422 Unprocessable Entity
503 also
carries id, model, usage and cost beside error. A 503, a 402, and
a 429 sent because the generator is busy do not count against the hourly
limit.
Draft a flow from call recordings
If you already have calls that go the way you want, from your current agent or your team, the API can learn the flow from them. Transcribe 2 to 10 recordings, then send the transcripts toPOST /v2/agents/flow/generate as examples.
The model learns the goal of the call, the steps and their order, the branches,
the objections and how each is handled, the language mix and tone, what is
collected, and how calls end. It then designs the flow for what happened in the
calls and their obvious variants.
Transcribe a recording
multipart/form-data:
Any other field, or a second file, is refused with
400 invalid_request.
200 OK
The transcript keeps the language and script as spoken: Hindi in Devanagari,
English words in Latin script, and numbers written as they were said. Personal
data is replaced with placeholders:
[NAME], [PHONE], [ADDRESS] and
[NUMBER]. When you draft from the transcripts, a placeholder for something you
know before the call, such as the customer’s name, becomes a call variable.
The audio is transcribed and not stored. It is deleted when the request
ends, and it is never logged. The transcript is not stored either: it exists
only in the response, so keep it if you want to draft again.
429 rate_limited with Retry-After.
Generate from the transcripts
Send the transcripts asexamples to POST /v2/agents/flow/generate, with the
same language and model fields as a brief:
One example is refused: the model would copy it rather than learn from it. A
brief sent with the examples steers the draft, and wins where it disagrees
with them. You can also write the transcripts yourself, in the same format. The
model never copies a caller’s personal data or one-off facts into the flow. A
draft from example calls can have up to 20 nodes, and takes longer than a draft
from a short brief. The examples below use Claude Opus 5.5, as Agent Studio does.
- cURL
- Python
- Node.js
input_schema, then
test it in the browser and take every branch
the calls took before it calls anyone.
What transcription costs
Each transcription is charged to your wallet at what the provider reports it cost, plus 20%, like a draft. The provider prices audio higher than text, so the charge does not follow a token rate. It comes to about ₹1 per minute of audio. The response’scost says what was charged.
- A transcript is charged, and so is a recording with no conversation in it
(
422 transcription_failed): the model listened to the audio either way. - Nothing else is charged. A request refused before the model runs (
400,402,429) and a model that is unavailable (503) cost nothing. - The wallet must hold a positive balance. If it is empty, the request is
refused with
402 insufficient_balancebefore the recording is decoded. - Where it shows. Each charge is one wallet transaction of kind
flow_generation, and the billing statement lists it with the AI flow drafts.
Transcription errors
422 uses the standard envelope, plus the charge:
422 Unprocessable Entity
402, a 503 and a 429 sent because
the transcriber is busy do not count against the hourly limit.
The graph
A flow is a JSON object withnodes, edges and, optionally, viewport. Any
other key is refused.
viewport, a node’s position, and an edge’s type and animated are for
visual editors. They accept any JSON value and are returned exactly as sent.
The call ignores them.
Nodes
Node data
Every default is filled in when you save, so a graph you read back states
exactly how each node behaves.
Transitions
Each entry inedges is one transition.
Extraction
An extraction variable is a value the model reads out of the conversation.
Extraction reads the whole conversation up to that point. It runs in the
background when the call leaves the node, so it never delays the agent’s
reply. The node the call ends on is extracted as the call ends. If two nodes
extract the same
name, the later value wins: a correction step can overwrite
what an earlier step heard.
Variables in node text
Node prompts (the global node’s too), greetings and extraction prompts are filled in from the call’svariables.
Transition conditions and labels are never filled in. Write them without
placeholders.
If your agent declares an
input_schema, every variable a node reads must be
declared in it. For a dotted path, its first part must be declared. The time
placeholders need no declaration. An undeclared variable is refused when you
save, with the path of the text that reads it.
Rules
A graph is refused unless all of these hold:- There is exactly one
startCallnode, at least oneendCallnode, and at most oneglobalNode. - Node IDs are unique. Transition IDs are unique. Labels are unique among the transitions leaving one node. Extraction variable names are unique within a node.
- No transition leaves an
endCallnode or theglobalNode, and none enters theglobalNode. - Every node except the global node can be reached from the start node, and has
a path to an
endCallnode. - The graph stays within the limits.
- Each name in a node’s
data.toolsrefers to anon_calltool defined on the agent. A node offers up to 10 distinct tool names; none may match a transition label anywhere in the graph. End and global nodes cannot offer tools. - The
end_calltool is disabled or absent. A flow ends at its end nodes. - Your tier’s model makes native tool calls, because every transition is a tool call. The standard model on every tier does.
Build one: a delivery confirmation
This example confirms a delivery slot with a customer, in Hindi. It checks who answered before it mentions the order, offers the slot, finds a new day if needed, and closes with a line the caller cannot cut off. You need yoursk_live_ key, a phone number you are allowed to call, a public
HTTPS URL for webhooks, and jq for the cURL examples.
Step 1: Write the graph
Save this asdelivery-flow.json.
delivery-flow.json
personaholds what every step shares: who the agent is, the language, reply length, and the rule to move on silently. That keeps each step’s own prompt short.identitydiscloses that the caller is speaking with an AI, and says nothing about the order until the right person has confirmed.slotandrescheduleeach name the transitions they may call, and when. Each transition’s condition repeats that decision in plain words.goodbyesetsallow_interrupt: false, so the summary of what was agreed is always heard in full.
Step 2: Validate it
input_schema variables and names that do not resolve to an agent’s
on-call tools are reported when you save the graph on that agent. A bare graph
has neither the input schema nor the agent’s tool definitions.
- cURL
- Python
- Node.js
200 OK
flow_revision is a fingerprint of the graph: the same graph always has the
same value, and any change produces a new one.
A graph with problems returns every one of them, each with its path:
400 Bad Request
flow on every route, so the same paths work whether you send
the graph to this dry run, to PUT /v2/agents/{id}/flow, or inside an agent.
Reachability (a node that cannot be reached, or has no path to an end) is
checked once the rest of the graph is valid.
Step 3: Create the agent
Send the graph as the agent’sflow. With a flow, system_prompt and
first_message are optional and unused: node prompts give the instructions and
the start node’s greeting opens the call.
- cURL
- Python
- Node.js
201 Created: the agent object, with
revision and the stored flow. Every node comes back with its defaults filled
in:
201 Created (excerpt)
Step 4: Test it in the browser
A browser call is the quickest way to test: you speak to the agent from a web page and watch it move through the graph. Create the call from your backend withchannel: "web" and the variables the nodes read.
- cURL
- Python
- Node.js
id: line of each event is left out above.)
The full event reference is in Browser calls.
Phone calls have no live event stream.
Try each branch before you dial anyone:
Step 5: Call a phone
The same agent takes phone calls. Sendto instead of channel: "web".
- cURL
- Python
- Node.js
202 Accepted, and the call follows the usual
lifecycle and webhooks. To run the same
agent over a list, create a campaign with its agent_id.
Step 6: Read the outcome
A node-based agent’s call carries aflow block: where the call finished, the
steps it went through, and every extracted value. It is on
GET /v2/calls/{id}, on each call in
GET /v2/calls, and at data.call.flow in
call.processed.
Match on
status for your own logic, and keep a default branch: new values can
be added. Values extracted as the call ends are included, so the block is the
complete record of the flow.
When it arrives. The block is absent until the call’s outcome is recorded,
usually within seconds of the call ending, so it can be missing from the
terminal event. A node-based agent’s call always sends call.processed when
the call has a webhook_url (unless the agent sets
emit_processed_webhook: false). That event waits up to 2 minutes for the outcome,
and is sent without flow if it has not arrived by then; read
GET /v2/calls/{id} later in that case.
Edit the graph
Use these three routes to change a saved graph:GET returns the graph with its agent ID and revisions:
200 OK
GET returns 404 not_found.
To edit safely, read the graph, change it, and save it with
if_revision set to the agent_revision you read. If someone saved the agent
in between, the save is refused with 409 conflict and nothing changes. Read
again and reapply your edit.
- cURL
- Python
- Node.js
200 OK: the updated agent object. PUT applies the same checks as
creating an agent, and reports problems as invalid_flow. On
PATCH, send if_revision in the body instead.
To turn a node-based agent back into a single-prompt agent, send
"flow": null. A single-prompt agent needs a prompt and an opening line, so
send system_prompt and first_message in the same PATCH if the agent has
none:
Versions
The graph is part of the agent, so it is versioned with the agent. There is no separate history for graphs.- Every save creates a revision. Creating the agent, a
PATCH, and aPUT /floweach save a new, immutable agent revision.revisionon the agent is the current one. - Read the history.
GET /v2/agents/{id}/revisionslists revisions newest first;GET /v2/agents/{id}/revisions/{revision}returns one, graph included. - Pin a call.
agent_revisiononPOST /v2/callsruns that saved revision, graph and all, instead of the current one. - Campaigns stay put. A campaign keeps the agent revision it was created with. Editing the graph does not change a campaign that is already running.
- Roll back.
POST /v2/agents/{id}/rollbackwith{"if_revision": <current>, "revision": <old>}copies an old revision into a new draft. A draft takes no ordinary calls. Publish it withPOST /v2/agents/{id}/publishand{"if_revision": <current>}to take calls again.
Calls with a node-based agent
Placing a call, and creating, starting or resuming a campaign, check again
that your tier’s model can run flows. If it cannot, the request is refused
with
400 invalid_flow and nothing is dialled.
Limits
Character limits count characters, not bytes. The size limit is checked on the
graph you send.
Errors
Graph problems use their own shape, like tool validation:error is the string invalid_flow and details lists every problem.
400 Bad Request
Everything else uses the standard error envelope:
Design tips
- One job per node. A node that confirms identity should not also explain the account. Short, single-purpose nodes move more reliably.
- Name the transitions in the node prompt. Say which function to call, and
when: “If they confirm, call
confirmed_identity.” Then write theconditionto match. - Move silently. Tell the model to call a transition without speaking first. The next node’s prompt decides what is said next, so nothing is said twice.
- Make conditions exclusive. Two transitions from one node that could both be true make the model guess. Say what distinguishes them.
- Put shared rules in the global node. Write the persona, language, reply length and what never to promise there once, instead of repeating them in every node.
- Keep end nodes to one line. An end node gets one reply before the call ends, so don’t ask a question there.
- Extract only what you use. Every extraction variable is stored with the call. Do not extract personal data you do not need.
Privacy and compliance
- Say it is an AI. Put the disclosure in the start node’s
greeting, as the example does. See Limits & compliance. - Confirm the person first. Give nothing away until the right person has confirmed, and give a wrong-person branch its own end node.
- Mind what you extract. Extracted values are stored with the call and delivered to your webhook. Treat them like the transcript.
- Check your rights to a recording. Transcribe only calls you have the right to process, with the notice or consent your calling rules require. The audio is not stored and personal data is replaced with placeholders, but a transcript is still your customers’ conversation: keep it like a call transcript.
- Calling rules still apply. Calling windows, consent and do-not-call rules are the same for every agent. See India calling rules.
Troubleshooting
Related
Agents
Every agent field, including
flow.Browser calls
Audio, live events and control for browser calls.
Calls
Place calls, read status, transcripts and recordings.
Tools
Pre-call, per-node on-call, and post-call tools for node-based agents.