What you’ll build
- A candidate opens your page, allows the microphone and talks to an AI interviewer that greets them by name.
- The page shows live captions for both speakers and whether the interviewer is listening, thinking or speaking.
- The interview keeps time on its own. At 8:00 the interviewer stops starting new topics, at 9:30 it thanks the candidate and says goodbye, and 10:00 is a hard stop. If the candidate gives no answer for 60 seconds, the interviewer closes politely.
- While the interview runs, a moderator on your side can nudge the interviewer or end the interview with a spoken goodbye.
- When it ends, your backend receives one webhook with the full transcript and a structured score, tagged with your own interview and candidate IDs.
- No audio is stored, so the transcript is your record of the interview.
sk_live_…) and webhook secret
(whsec_…), a backend in Python 3.10+ or Node.js 18+, and an HTTPS URL that
can receive webhooks (a tunnel to your laptop works while you build).
How it works
Step 1: Create the interviewer
An agent holds everything that is the same for every interview of one kind: the interviewer’s instructions, voice and language, how it ends a call, and how the finished conversation is scored. Everything about one candidate goes on the call in step 2.Write the instructions
The agent’ssystem_prompt holds up to 8,000 characters. An interview guide
fits comfortably if it contains only what the interviewer needs during the
conversation. Move anything that is only needed afterwards, such as the scoring
rubric, into analysis, which has its own
instructions.
This layout works well for voice interviews:
{{placeholders}} are filled per interview from the call’s variables
(up to 32 variables, 512 characters each). One agent then serves every
candidate for that role type. See the prompting guide for
more on writing for voice.
If your interview guide is longer than 8,000 characters
The 8,000-character limit onsystem_prompt is the same for every workspace,
and there is no per-call system prompt. A call can add its variables, its
first_message and, while it runs, instructions.
Long interview guides usually carry material the interviewer does not need while
it talks. Move each part to where it is used:
Score the interview automatically
Turn onanalysis and the agent scores each finished interview against your
rubric. The result arrives with the transcript in step 5.
Create the agent
- cURL
- Python
- Node.js
Step 2: Start an interview from your backend
When a candidate opens the interview page, your backend creates the call with everything specific to this candidate, stores the call ID beside its own interview record, and gives the page two links.Idempotency-Key built from your interview ID. If the request is
retried, you get the same call back instead of a second one.
- Python
- Node.js
ws_urlis the audio socket. It works only once and must be opened within 5 minutes. If the candidate refreshes the page, create a new interview.events_urlis the live caption stream. You can reopen it at any point during the interview and for one hour after it ends.- If the page never connects, the call ends as
failedwithended_reason: "no-media"and is not billed.
Step 3: Build the interview page
The page asks for the microphone, starts the interview, plays the interviewer’s voice, and shows captions, the interviewer’s state and a countdown. The audio format is described under Audio socket on the Browser calls page.- Muting while the interviewer speaks. With the box ticked, the page sends
silence while
agent.stateisspeaking, so room noise is never taken for an answer and the candidate cannot interrupt.speakingfollows the actual audio, not the text. Untick it to let candidates talk over the interviewer, which feels more natural in a quiet room. Keep sending frames either way: silence keeps the line open, and the silence timer counts only real speech. - Captions. Each caption appears when its turn is complete. An interrupted interviewer turn shows only the words the candidate actually heard.
- Leaving. Closing the audio socket ends the interview immediately. To end with a spoken goodbye instead, call your backend’s end route from step 4.
Step 4: Let a moderator steer the interview
While the interview runs, a moderator, such as a recruiter watching the captions, can change the interviewer’s direction or end the interview with a goodbye. Your backend does both through live control.- An instruction is added to the interviewer’s context from its next reply and stays for the rest of the interview. It is never read aloud and does not appear in the transcript.
- A close speaks a goodbye line and ends the interview as
completed, withended_reason: "api-ended-call".
- Python
- Node.js
events_url, or read the stream server-side with your API key. See
live events.
End at once
close lets the interviewer say goodbye first. When the interview must stop
immediately, for example because the candidate reports a problem, abort it
instead. The audio stops at once and the page receives call.ended. The call
ends aborted and is not billed.
Optional: give the interviewer time checks
The timing policy already handles the wrap-up at 8:00 and the goodbye at 9:30. If you also want the interviewer to pace itself earlier, send it the remaining time as instructions. Start the clock on thecall.started webhook: it fires
when audio goes live, which is the moment the timing policy counts from.
- Python
- Node.js
Step 5: Receive the transcript and the score
Your webhook receives these events for each interview, in order:call.startedwhen the candidate’s audio connects. Use it to start any time checks from step 4.- A terminal event as soon as the interview ends:
call.completed,call.failedorcall.aborted. Mark the interview as over. call.processed, after the transcript has settled (within about two minutes) and the score is ready. It carries the transcript indata.transcriptand the score indata.call.analysis.
metadata, so you can find the interview without a
lookup table. Verify the signature on every delivery, and ignore any event whose
id you have already seen. The Webhooks page explains
signature verification.
- Python
- Node.js
call.processed mean for an interview:
GET /v2/calls/{id} (it reports transcript_status) and
GET /v2/calls/{id}/transcript at any time.
Test it
Short timers show every behaviour in about two minutes. Use them while you build, then switch back to the 10-minute values.- Talk for the full two minutes. At 1:00 the interviewer stops starting new
topics. At 1:40 it says the close message and the page shows “Time’s up”.
Your webhook receives
call.completed, thencall.processedwithended_reason: "time-limit-close", the transcript and a score. - Say one sentence, then stay silent. About 30 seconds later the
interviewer says the silence message and the interview ends as
user-silence-close. - Steer and end from the moderator routes. An instruction returns
appliedand the next reply follows it. The end route makes the interviewer say goodbye; the interview ends asapi-ended-call.
Privacy and compliance
- Tell candidates up front that they are talking to an AI interviewer, that the conversation is transcribed, and how the transcript and score will be used. Get their consent before the interview starts.
- With
recording_enabled: false, no recording is stored andGET /v2/calls/{id}/recordingreturns404. The transcript is kept. See data retention for how long. To turn recording off for your whole workspace, ask help@miraiminds.co. - Put only IDs in
metadata. Never put names, contact details or interview answers there. - Keep a person in the loop for hiring decisions. Use the score to sort and review interviews, and do not use it to reject candidates automatically.
ws_urlandevents_urlgive access to one interview. Send them only to that candidate’s page over HTTPS, and keep them out of logs and analytics. Your API key stays on your server.- Treat captions as untrusted text and render them with
textContent, never as HTML.
Production checklist
- One agent per interview type, with a Timekeeping section and an
analysisrubric. -
metadatacarries your interview and candidate IDs; your database stores the returned callid. - Every create uses an
Idempotency-Keybuilt from your interview ID. -
max_duration_secs,timing,recording_enabled: false,final_results: trueandwebhook_urlare set on every interview. - Moderator routes check that the caller is your staff and use idempotency keys.
- The webhook verifies signatures, ignores repeated event IDs, answers
quickly and processes
call.processedin the background. - Your interview status is driven by the terminal event, and your
transcript processing by
call.processed. - Candidates see an AI disclosure and give consent before the interview starts.
- You ran the three test interviews above on the tier you will use in production.
Common questions
Can we send an instruction to the agent while the call is live?
Can we send an instruction to the agent while the call is live?
POST /v2/calls/{id}/control with
{"type": "instruction", "text": "…"}. The agent follows it from its next
reply. If the action starts in the browser, the page asks your backend, which
holds the key. See step 4.Is there a built-in way to wrap up before the time limit?
Is there a built-in way to wrap up before the time limit?
timing on the call gives the agent a wrap-up instruction a set number
of seconds before the cap, then a spoken goodbye before the cap, and a polite
close after a stretch of user silence. The clock starts when audio goes live.
To make the agent pace itself earlier, send
time checks.Can we end a live browser call from our side?
Can we end a live browser call from our side?
id. Use a close control for a
spoken goodbye, or POST /v2/calls/{id}/abort to stop at once. See
step 4.Does the page receive each turn as text while the call runs?
Does the page receive each turn as text while the call runs?
events_url with EventSource: every completed turn arrives as a
transcript.turn event for both speakers, in real time. Text arrives per
completed turn, not word by word. Audio travels over a WebSocket (ws_url)
and events over Server-Sent Events, so you need no other client library. See
step 3.Can the page tell when the agent is speaking, listening or thinking?
Can the page tell when the agent is speaking, listening or thinking?
agent.state events report speaking, listening, processing and
ended. speaking follows the actual audio, so it is safe for muting the
microphone while the agent talks, as the page in step 3 does.Can the system prompt be longer than 8,000 characters?
Can the system prompt be longer than 8,000 characters?
Can the opening line be set per call?
Can the opening line be set per call?
first_message on the call replaces the agent’s opening line for that
call only. Your backend can write it in full, with no placeholders. If you do
use a {{placeholder}} and its value is missing, it is replaced with nothing,
so the agent never reads out the braces.Is our metadata sent back in webhooks for browser calls?
Is our metadata sent back in webhooks for browser calls?
data.call.metadata of every webhook for the call and on
GET /v2/calls/{id}. The create response also returns the call id to store.Which webhook means the session is over, and which means process it now?
Which webhook means the session is over, and which means process it now?
call.completed, call.failed or call.aborted) means
the conversation is over. call.processed, sent when you create the call with
final_results: true, means “everything is ready”: it carries the transcript
and the score. Process the session on call.processed. See
step 5.Can the webhook URL be set per call?
Can the webhook URL be set per call?
webhook_url on the call applies to that call only.How do we get the transcript, and when is it ready?
How do we get the transcript, and when is it ready?
call.processed (data.transcript.turns), and you can
fetch it any time with GET /v2/calls/{id}/transcript. It is normally ready
by the time the terminal webhook arrives; transcript_status on the call
says ready. call.processed follows within about two minutes of the call
ending, plus the time scoring takes.Can we keep text only, with no audio?
Can we keep text only, with no audio?
recording_enabled: false: no audio is stored, and
captions, the transcript and scoring still work. To turn audio off for your
whole workspace, ask help@miraiminds.co. See
data retention for how long transcripts are kept.We use the older POST /v2/call/web endpoint. Does it support this?
We use the older POST /v2/call/web endpoint. Does it support this?
POST /v2/calls
with channel: "web". Build new browser sessions on it.