Skip to main content
A node-based agent (a flow) runs a conversation as a graph. Each node is one step of the call with its own instructions. Each transition between nodes is a condition, written in plain language, that the model checks as the caller speaks. When a condition is met, the call moves to the next step and that step’s instructions take over. Node-based agents are available on every tier. They take phone calls, browser calls and campaigns, and their calls are billed like any other call. A flow is one field on the agent, 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

  1. The call opens on the start node. Its greeting is spoken exactly as written, with {{variables}} filled in. With no greeting, the model opens the call from the start node’s prompt.
  2. The model follows the current node. Its instructions are the global node’s prompt (when the node’s add_global_prompt is true), then the current node’s prompt.
  3. Transitions are functions. Each transition out of the current node is offered to the model as a function with no arguments: label is its name and condition is its description. When the model decides the condition is met, it calls the function and the call moves to the target node.
  4. 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.
  5. 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.
  6. An end node ends the call. The agent speaks its reply to the end node’s prompt, then hangs up. The call ends completed with ended_reason: "assistant-ended-call".
  7. Extraction runs as the call leaves a node. A node with extraction_enabled has its extraction_variables extracted from the conversation in the background when the call moves on. The node the call ends on is extracted as the call ends.
Interruptions and silence follow the same rules as every agent:
  • 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: false the 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 with timing.user_silence_close_secs uses 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
Review and test every draft before it takes a real call. Read each node’s prompt and each transition’s condition, and check that the draft asks, records and refuses what you intended. Then run a browser test with live events and take every branch at least once.

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’s cost 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 a 503 after the model had already answered, are charged for what the model did. Both responses carry id, model, usage and cost. 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_balance before 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

Save it on an agent

Create the agent with the draft’s flow and its input_schema. Save them together: when an agent has an input_schema, every variable a node reads must be declared in it.
With an 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 as flow 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.
Test the revised agent again before it takes real calls. A revision is a new agent revision like any other save, so you can roll it back.

Generation errors

422 uses the same shape as invalid_flow, plus the charge:
422 Unprocessable Entity
The other errors use the standard envelope. A charged 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 to POST /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.
In Agent Studio, choose Create agent, then Node-based. Describe the calls, add 2 to 10 recordings, or both, then choose Draft flow. To write the description with ChatGPT or Claude, choose Copy prompt, paste the prompt there, and paste the answer back as the description. Agent Studio drafts with Claude Opus 5.5 and creates the agent, ready to test and edit in the builder.

Transcribe a recording

Send one recording per request, as 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.
A two-minute call takes about 5 to 10 seconds to transcribe. Set a client timeout of a few minutes to allow for the upload. Transcribe one or two recordings at a time: while the transcriber is busy with other requests, it answers 429 rate_limited with Retry-After.

Generate from the transcripts

Send the transcripts as examples 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.
The response is the same draft a brief returns. Save it on an agent with its 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’s cost 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_balance before 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
A recording that cannot be decoded, a 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 with nodes, 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 in edges is one transition.
Write labels in lowercase snake_case, such as confirmed_identity. The model sees each label in lowercase, and a readable name helps it choose.

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’s variables. 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 startCall node, at least one endCall node, and at most one globalNode.
  • 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 endCall node or the globalNode, and none enters the globalNode.
  • Every node except the global node can be reached from the start node, and has a path to an endCall node.
  • The graph stays within the limits.
  • Each name in a node’s data.tools refers to an on_call tool 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_call tool 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.
Each problem is reported with its path. See errors.

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 your sk_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 as delivery-flow.json.
delivery-flow.json
What each part does:
  • persona holds 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.
  • identity discloses that the caller is speaking with an AI, and says nothing about the order until the right person has confirmed.
  • slot and reschedule each name the transitions they may call, and when. Each transition’s condition repeats that decision in plain words.
  • goodbye sets allow_interrupt: false, so the summary of what was agreed is always heard in full.

Step 2: Validate it

The body is the graph. The dry run applies every graph rule and checks that your tier’s model can run flows. It saves nothing and calls no one. Undeclared 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.
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
Paths start at 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’s flow. 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.
201 Created: the agent object, with revision and the stored flow. Every node comes back with its defaults filled in:
201 Created (excerpt)
This is how the other agent fields behave on a node-based agent:

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 with channel: "web" and the variables the nodes read.
Open the two links from a page. The browser example is a complete page; add two listeners to show the current step and what each step heard:
Your backend can read the same stream with the API key. Here is an abridged test in which the customer asks for another day:
(The 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. Send to instead of channel: "web".
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 a flow 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
A single-prompt agent has no graph, so 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.
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:
An edit takes effect on the next call. Calls already queued or running keep the graph they started with.

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 a PUT /flow each save a new, immutable agent revision. revision on the agent is the current one.
  • Read the history. GET /v2/agents/{id}/revisions lists revisions newest first; GET /v2/agents/{id}/revisions/{revision} returns one, graph included.
  • Pin a call. agent_revision on POST /v2/calls runs 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}/rollback with {"if_revision": <current>, "revision": <old>} copies an old revision into a new draft. A draft takes no ordinary calls. Publish it with POST /v2/agents/{id}/publish and {"if_revision": <current>} to take calls again.
Publishing and rolling back check the graph again, including your tier’s model.

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
Problems are found in stages: first the graph’s own rules, then the agent’s (declared variables and on-call tools), then your tier’s model. Each stage reports all of its problems at once. Fix them and save again to see the next stage. 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 the condition to 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

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.