Skip to main content
Turn on analysis on an agent and every call it finishes is read once more after the hangup. You get a summary, a success verdict against your own criteria, the fields you asked for in output_schema and, if you give the agent a list of message templates, the template to send next with its slots filled in from the conversation. Analysis runs after the call, so it never slows the conversation down. The result is on the call, on its own endpoint and in the call.processed webhook. Base URL https://sandbox.voice.miraiminds.co. All endpoints require Authorization: Bearer sk_live_….

Turn it on

Send analysis on POST /v2/agents or PATCH /v2/agents/{id}. A PATCH replaces the whole analysis object, so send every field you want to keep. "analysis": null turns analysis off. A call runs the analysis settings of the agent revision it was placed with, so a change applies to calls placed after it.

Where the result appears

On the call object, in GET /v2/calls/{id} and in data.call of call.processed:
status is done, failed or skipped. A call that never connected is skipped, because there is no conversation to read. The same result, with what we know about sending it to you, is at its own endpoint:
200 OK
state is pending until the analysis has run. If it ends failed, retry it with POST /v2/calls/{id}/analysis/retry and the body {"if_generation": <the generation you read>}. The retry runs as the next generation. A post-call tool can read the result as {{ analysis.summary }}, {{ analysis.success }} and {{ analysis.data.<field> }}.

Pick a follow-up message

Many calls end with a message, such as a WhatsApp template with a payment link or an SMS confirming a date. analysis.templates gives the agent the list of messages you can send. After each call it picks the one that fits, or none, and fills in the template’s variables from what was said. The choice is one extra call to the analysis model, made after the analysis finishes. It reads your template list, the analysis and the transcript. The model is told to fill a variable only with a value the conversation or the analysis established, and every slot it leaves empty is listed as missing. Choosing a template costs nothing extra. Give the list in one of two ways:
  • items: the templates written on the agent. Use this for a short, fixed list, such as approved SMS templates.
  • fetch: a request we send to your system after each call to read the current list. Use this when templates are added and approved outside Mirai, as they usually are for WhatsApp.
Set exactly one of the two. templates requires analysis.enabled: true.

The templates object

Each template in items: Declare only the slots the conversation can fill. A payment link, a ticket number or a fixed sender name are yours to fill when you send the message. Your instructions reach the model as instructions. The template list and the transcript reach it as data, and the model is told not to follow instructions that appear inside them.

Templates from your system

With fetch, we read your template list after every call, so a template you add or retire is used from the next call without editing the agent.
The request is written like a post-call HTTP tool’s request and follows the same rules:
  • {{ }} placeholders can read vars.* (the call’s variables), call.* (such as call.id, call.agent_id, call.to and call.metadata.<key>), now.*, analysis.* (the analysis that just finished) and secrets.*. Inside body, "{{= call.metadata }}" sends the whole object with its JSON type. tools.* is refused, because the fetch runs before the post-call tools.
  • Keep credentials in workspace secrets and reference them as {{ secrets.<name> }}. A secret that does not exist is refused when you save the agent, and a secret the fetch uses cannot be deleted until you remove it from the agent (409 secret_in_use).
  • The same outbound network policy applies: public addresses only, no redirects. The request carries the X-Mirai-Call-Id header.
  • Each attempt has 5 seconds. A timeout, a network error or a 5xx is retried once. Your answer must be JSON and at most 256 KB.
A fetch is not a tool run, so it does not appear in the call’s tool receipts.

Answers we can read

We read the list leniently, so most template APIs work as they are.
  1. With items_path, the list is at that path.
  2. Without it, the answer itself can be the list, or we take the first list we find under templates, data, Data or items.
Inside each entry we read name, language, description and variables, and ignore every other field. variables can be objects with key and description, or plain strings or numbers that are the keys. An entry can also be a bare string, which is a template with no variables. An entry without a name, or with a name already seen, is dropped, and we use the first 200 templates. All of these answers work:

The result: analysis.template

The choice arrives as template inside the analysis, wherever the analysis appears: the call object, GET /v2/calls/{id}/analysis and call.processed.
A template was chosen
No template fits
The choice could not be made
Check missing before you send. A template with an empty slot usually cannot be sent as it is, so decide on your side whether to fill it yourself, send another template or pass the call to a person. template is present only when the agent revision the call ran with had analysis.templates and the analysis completed. Calls of other agents carry no template key at all.

A template problem never fails the analysis

When the choice cannot be made, whether the fetch timed out or the model named a template that is not in the list, you get template.status: "failed" with its code. The summary, success and data still arrive, the analysis status is still done, and post-call tools still run.

Choosing again

POST /v2/calls/{id}/post_call/rerun chooses again before it sends the new call.processed version, and a fetch reads your list again. Use it after you fix a template endpoint that was down. The rerun uses the templates settings the call ran with. If the new choice cannot be made, the rerun still runs and publishes the template it had. A retried analysis chooses a template when the retry completes.

Send the message from a post-call tool

A post-call tool can read the choice as {{ analysis.template.name }}, {{ analysis.template.language }} and {{ analysis.template.variables.<key> }}. Inside a body, "{{= analysis.template.variables }}" sends the whole map.
Your endpoint still checks missing before it sends. If you would rather decide in your own backend, skip the tool and act on call.processed.

Example: WhatsApp follow-up

An order confirmation agent ends some calls with a WhatsApp message. The approved templates live in your messaging system, and each brand you serve has its own set, so the agent reads the list for the call’s brand after every call. Store the key for your template API once:
Then set analysis on the agent:
Place each call with the brand in its metadata, for example "metadata": { "brand_id": "brand_42" }. After the call, we send your template API:
and it answers with that brand’s approved templates:
The customer agreed to pay online, so call.processed carries:
data.call.analysis.template
Your webhook handler, or a post-call tool, sends payment_link_hi to data.call.to with those two values and adds the payment link itself.

Example: SMS from a fixed list

An EMI reminder agent ends calls with one of three registered SMS templates. SMS templates registered with Indian operators (DLT) mark each slot as {#var#} and change rarely, so the list lives on the agent. Number the slots in the order they appear in the text and describe each one, with the format your template needs.
The customer said they would pay “next week” without naming a day:
data.call.analysis.template
Slot 3 is in missing because no date was agreed. Your system can hold the SMS and schedule a call back, or send another template that needs no date.

Errors

A problem in analysis.templates is a 400 invalid_request when you save the agent, and the message names the field. The common ones: Unknown fields inside templates, a template or a slot are refused too. Failures after the call never produce an error response. They show up as template.status: "failed" with the codes above.