Skip to main content
Every call has a live event stream. While the call runs, it sends the agent’s state and each completed turn, so your own dashboard or app can show a phone call as it happens: whether the agent is listening or speaking, and what each side said. The stream is GET /v2/calls/{id}/events, sent as Server-Sent Events, and it is read-only. The events and their format are the same for outbound phone calls, campaign calls, inbound calls on your numbers, browser calls and realtime sessions. The base URL is https://sandbox.voice.miraiminds.co. API requests use Authorization: Bearer sk_live_….

Which calls have a stream

Every call has one. How you open it depends on how the call started: A campaign call or an inbound call has no create response, so it has no events_url. Read its stream from your backend with your key.

Follow a phone call

Place the call from your backend as usual. The 202 carries events_url, the call’s stream:
202 Accepted
A phone call’s 202 has no ws_url and no transport. Those belong to browser calls, where the page also needs an audio link. You can open the stream as soon as the call is placed. It stays quiet while the phone rings, and the first event arrives when the call is answered.

A call nobody answers

A call that is never answered (busy, no answer, or failed at the carrier) sends exactly one event, call.ended, as soon as the call ends:
status is the call’s final status, such as busy, no_answer or failed. synthesized: true means no conversation ran, so we wrote the end of the stream ourselves.

Open the stream

From a browser, with events_url

events_url carries its own read-only token for this one call. The token is valid for the call’s maximum duration plus one hour. Hand the link to the page that shows the call and open it with EventSource. Your API key never reaches the page.
The stream allows every origin (Access-Control-Allow-Origin: *) and uses no cookies, so a page on your own domain can open it directly. The token in the link is the credential.
  • Use the link exactly as returned, and do not build it yourself.
  • If you retry the create request with the same Idempotency-Key, the replayed 202 repeats events_url while its token is valid. After that, open the stream with your key.
events_url is a credential for one call. Anyone holding it can read that conversation while the token is valid. Send it only to the page that shows this call, over HTTPS, and keep it out of logs and analytics. Never send your sk_live_ key to a browser.

From your backend, with your API key

Your key opens the stream of any call in your workspace. This is how you follow campaign and inbound calls, which have no events_url.
-N turns off curl’s buffering, so each event prints when it arrives.

Responses


Show phone calls on your dashboard

This server places phone calls and relays each call’s stream to your dashboard with your API key. The dashboard reads the relay from your own origin, so your own sign-in decides who can watch which call. The relay works for campaign and inbound calls too, because it only needs the call id. Add those calls to db.calls when their call.started webhook arrives (data.call.id). It is written in Node.js 18 or later with Express. db and requireUser stand in for your own code.
server.js
The dashboard page opens the relay with EventSource and shows the agent’s state and the conversation:
call.html
Notes on the example:
  • For a call you place yourself, you can skip the relay: return events_url from POST /api/calls and open it in the page with EventSource. The relay is for campaign and inbound calls, and for when your own sign-in should decide who can watch.
  • If a proxy sits in front of your server, turn off response buffering for the relay route. Otherwise events reach the page in bursts.
  • A call nobody answers ends at once on the page, with a line such as Call ended: no_answer.

Framing

Each event is one SSE message with three lines:
  • id is the position in the stream. Treat it as opaque: it is what a reconnect resumes from.
  • event is the event type. Because every event is named, listen with addEventListener(type, …). onmessage receives nothing.
  • data is a JSON envelope:
Treat the envelope as additive: new fields and new event types can appear. Ignore what you do not recognise.

Event types

agent.state

On a browser call, the page’s own playback buffer can add a short delay between speaking and the moment sound comes out of the speaker.

transcript.turn

One event per completed turn, for both speakers.
Text arrives once per completed turn: the person’s after they finish speaking, the agent’s after its turn ends. There are no word-by-word captions yet.

turn.metrics

How fast one agent reply came. It is sent when the reply’s audio starts, and again when later figures arrive, such as when the reply finishes playing. Each event carries everything known so far for that reply, so keep the latest one per reply.
A field is left out until it is known.

tool.started and tool.completed

The agent ran one of its on-call tools. tool.started is sent when the request to your endpoint goes out, and tool.completed when the result is back. Only names and timings are sent, never the tool’s arguments or results.

tool.missing

The model asked for a tool this agent does not have. The agent was told so and carried on. This usually means the prompt mentions a tool that was never added.

flow.node.entered

Sent only for a node-based agent: the call entered a node. The start node sends one too, with previous_id: null. It arrives before the reply that node produces, so a page can show the current step as it changes.

flow.variables.extracted

Sent only for a node-based agent: a node’s extraction saved values. It arrives shortly after the call leaves a node with extraction_enabled, and only when something was extracted.

call.ended

Always the last event. The stream closes after it.
call.ended is sent even when the call ends abnormally. Use it to update what your page shows. The webhook is the record your backend acts on.

Reconnecting

  • Automatic resume. EventSource reconnects on its own and sends the Last-Event-ID header. The stream continues after that event, so no turn is missed or repeated.
  • Manual resume. Add after=<id> to events_url, or to the stream URL you open with your key, to start after a given event, for example after a page reload.
  • One-hour buffer. Events are kept for one hour after the call ends. Opening the stream for a finished call replays what happened, then closes.
  • Past the end. A reader that has already received call.ended gets 204 No Content, and EventSource stops reconnecting.
  • Close it yourself. Call events.close() when you receive call.ended, so EventSource does not try to reconnect.
  • Keep-alives. The server sends an SSE comment every 15 seconds. EventSource ignores these; they keep proxies from closing an idle stream, for example while a phone rings.

Steering or stopping a call

The stream is read-only. To steer a browser call or a realtime session while it runs, send a live control. A phone call answers live control with 400 unsupported_channel. To stop a phone call, use POST /v2/calls/{id}/abort.

Privacy

  • The stream carries what your customer says. Show it only to people who need it, behind your own sign-in.
  • Live events work the same with recording_enabled: false. No audio is stored, and the text still streams.
  • Events are kept for one hour after the call ends. For the stored transcript, use GET /v2/calls/{id}/transcript or final results.

Calls

Place calls, read status, transcripts and recordings.

Browser calls

Audio, live events and live control in your user’s browser.

Campaigns

Dial a whole list. Each call has its own stream.

Webhooks

Signed events for each step of a call, and the final transcript.