Live call control
Live call control lets your server change a call while it’s in progress. You send each command as an HTTPS POST to the call’s controlUrl. Commands can make the assistant speak, add context to the conversation, mute either side, transfer the call, hand it off to another assistant or squad, or end it.
To stream a call’s audio and events instead, see Listen to live calls.
The control URL doesn’t require authentication by default. Turn on authentication so only requests with your public API key can control the call.
Get the control URL
Every call has its control URL in call.monitor.controlUrl. Read the URL from the call object instead of building it yourself. The host can change between calls.
The control URL accepts commands while the call is in progress. You can get it from the create call response, from server messages, or from the call record.
From the create call response
For outbound calls, the Create Call response includes monitor.controlUrl:
From server messages
For inbound calls, read the control URL from a server message. Server messages that include the call object carry it at message.call.monitor.controlUrl. The first status-update message with status set to in-progress arrives as soon as the call connects:
The tool-calls server message also includes the control URL, so a Function tool handler can control the call it’s running in. The assistant-request message doesn’t include the control URL.
The status-update message is in the default serverMessages list. If you set serverMessages on your assistant, include status-update, because your list replaces the default.
From the call record
Get Call returns monitor.controlUrl for any call. The URL stays on the call record after the call ends, so check that status is in-progress before you send a command.
Control settings
Live call control is configured in the assistant’s monitorPlan. It’s on by default and doesn’t require authentication by default.
For the Call Listen fields, see Listen settings.
Turn live call control on or off
Set controlEnabled to false to turn off live call control for an assistant. If any assistant on a call has controlEnabled set to false when the call starts, the call has no controlUrl.
A call that starts with control on can later reach an assistant with control off, such as after a handoff. From then on, every command returns 400 with this error:
Require authentication
Set controlAuthenticationEnabled to true to require your public API key on every command:
Then send the public API key in the Authorization header:
A request with no key, or with your private API key, returns 400 with {"error":"Unauthorized"}.
Authentication applies to the whole call. If any assistant on the call has controlAuthenticationEnabled set to true, every command on that call needs the public API key, including after a handoff to an assistant that doesn’t require it.
Control actions
Send each command as a JSON body to the control URL. The examples on this page set CONTROL_URL to the call’s control URL. If you turned on authentication, add the Authorization header to each request.
A 200 response with {"status":"ok"} means the command was accepted. It doesn’t mean the command finished. For example, a transfer can still fail after the 200 response.
Action reference
Speak a message
Send a say command to make the assistant speak exact text:
Optional fields:
endCallAfterSpoken: End the call after the message is spoken.interruptionsEnabled: Let the caller interrupt the message. Defaults tofalse, which ignores the caller’s audio while the message plays.interruptAssistantEnabled: Stop any speech already in progress or queued and say this message immediately. The assistant doesn’t resume the interrupted response. Defaults tofalse, which waits until the assistant finishes speaking.
Play an audio file
To play a pre-recorded audio file instead of synthesized speech, set content to the file’s URL:
Set content to a single https:// URL with no other text. Other values are spoken as text or don’t play, as the following table shows.
The audio file must meet these requirements:
- The URL is publicly reachable, or it’s a presigned URL. Vapi doesn’t send authentication headers when it downloads the file.
- The file is WAV or MP3.
- The file is short. Vapi downloads the whole file before playback starts, so shorter files start sooner.
Audio is cached by URL. To change an audio file, publish the new version at a new URL, such as hold-message-v2.wav. If you replace the file at the same URL, calls can keep playing the old audio.
Audio files aren’t added to the call’s transcript, messages, or artifact.messages.
Each say request plays one audio file when you send it. Audio doesn’t play automatically before each assistant turn. To play a sound at a specific point in the conversation, send a say request at that moment.
If Vapi can’t download or decode the file, nothing plays and the control request still returns a success response. Test each audio URL before you use it on live calls.
Add a message to the conversation
Send an add-message command to add a message to the conversation history. The message object takes a role of system, user, or assistant, and a content string.
The triggerResponseEnabled field controls what happens next:
true(default): The assistant responds right away.false: The message is added silently. The assistant uses it on its next turn.
This example adds a system message and has the assistant respond:
This example adds context without interrupting the conversation:
If the caller is speaking when a triggered response starts, the caller can interrupt it.
Mute or unmute the assistant
Send a control command with mute-assistant to stop the assistant from speaking. Send unmute-assistant to let it speak again.
While the assistant is muted, it still hears and transcribes the caller.
Mute or unmute the customer
Send a control command with mute-customer to stop the assistant from hearing the caller. Send unmute-customer to let it hear the caller again.
Muting the customer doesn’t change what anyone on the call hears. The assistant stops listening, and the caller’s speech isn’t transcribed. The caller gets no signal that they’re muted, so send a say command first if the caller should know.
The customer stays muted until you send unmute-customer. If you never unmute, the assistant can’t hear the caller for the rest of the call.
Say the first message
Send a control command with say-first-message to make the assistant say its first message. Use it when video recording is on and the conversation starts only after your client starts recording.
End the call
Send an end-call command to end the call:
The call ends with the assistant-ended-call-after-message-spoken ended reason. To say goodbye first, send a say command with endCallAfterSpoken set to true instead.
Transfer the call
Send a transfer command to transfer the call to a phone number or a SIP URI. The optional content field is spoken to the caller before the transfer.
To transfer to a SIP URI, set the destination type to sip:
With content, the transfer starts after the message is spoken. Without content, the transfer starts right away.
The destination object takes the same fields as a Transfer Call tool destination.
Hand off to an assistant or squad
Send a handoff command to move the call to another assistant or to a squad. The optional content field is spoken to the caller before the handoff.
To hand off to a saved assistant, set assistantId:
To hand off to an assistant defined inline, set assistant. This example also sets contextEngineeringPlan so the new assistant starts without the earlier conversation:
To hand off to a squad, set the destination type to squad and pass a squadId or an inline squad. This example defines the squad inline:
Use contextEngineeringPlan to choose the conversation history the destination receives. For every destination field, see Configure handoff destinations and Pass context and variables.
GPT-Live support
GPT-Live calls support a subset of live call control:
- HTTP
end-call append-contextto add speaker commentary, thinking, or instructions- Cold
transferon supported phone connections
The say, add-message, control, and handoff commands aren’t supported on GPT-Live calls. For request examples and limits, see the GPT-Live live call control guide.
What live call control can’t do
Live call control has a fixed set of actions. It can’t do these things:
- Pause and resume a call: There’s no pause action. Muting both the assistant and the customer is the closest option.
- Change the assistant’s settings: There’s no action to change the model, voice, or prompt mid-call. Add context with
add-message, or hand off to an assistant that has the settings you need. - Control a call that has ended: Commands only work while the call is in progress.
Troubleshoot control commands
Each entry starts with the response or symptom you see, then gives the cause and fix.
Commands return Unauthorized
A 400 response with {"error":"Unauthorized"} means the call requires authentication. Send your public API key in the Authorization header. A private API key is refused. See Require authentication.
Commands return a control-disabled error
This error means an assistant on the call has controlEnabled set to false:
Set controlEnabled to true on every assistant the call can reach. See Turn live call control on or off.
Commands return Call Not Active
A 400 response with Call `<id>` Not Active. means the call is no longer in progress. Don’t retry; check the call’s status and endedReason with Get Call.
Commands return failed to route call
A 500 response with failed to route call usually means the call has ended. This response can take about 30 seconds to arrive, so set your client timeout above 30 seconds. Retry once after 1 to 2 seconds. If it fails again, treat the call as ended.
A triggered response isn't heard
An add-message command with triggerResponseEnabled set to true returns 200, but the caller doesn’t hear a reply. The caller was likely speaking when the response started and interrupted it. Send the command when the caller is quiet, or use a say command with exact text instead.