Skip to content
VaakyoDocs
Navigation
Open console →

Agents

Call tab

The Call tab controls how a conversation runs and ends: interruptions, silence handling, the "are you still there?" check, hanging up with a prompt, the maximum length, calling hours and recording.

Fields

All fields live under call in the agent object.

FieldTypeDefaultAllowedSection
recordbooleantrueRecording
allow_interruptionsbooleantrueInterruptions
interruption_min_wordsinteger21 to 10Interruptions
check_user_onlinebooleanfalseUser online check
user_online_messagestringAre you still there?User online check
user_online_after_secondsinteger93 to 60User online check
hangup_after_silence_secondsinteger155 to 120Hang up on silence
hangup_on_promptbooleanfalseHang up using a prompt
hangup_promptstringsee belowup to 4,000 charactersHang up using a prompt
hangup_interruptiblebooleanfalseHang up using a prompt
hangup_messagestring""{variables} allowedHang-up message
max_duration_secondsinteger60030 to 3600Maximum length
call_start_hourinteger90 to 23Calling hours
call_end_hourinteger211 to 24Calling hours
timezonestringAsia/Kolkataan IANA time zoneCalling hours
ambient_soundstring"""", office, call_centre, cafe, street or upload:<id>Ambient noise
ambient_volumeinteger300 to 100Ambient noise
noise_cancellationbooleanfalseNoise cancellation
noise_cancellation_strengthinteger500 to 100Noise cancellation
voicemail_detectionbooleanfalseVoicemail detection
voicemail_detection_secondsinteger52 to 10Voicemail detection
voicemail_messagestring""up to 1,000 characters, {variables} allowedVoicemail detection
dtmf_inputbooleanfalseKeypad input
dtmf_wait_secondsnumber2.50.5 to 10Keypad input
auto_reschedulebooleanfalseAuto reschedule
reschedule_max_daysinteger71 to 30Auto reschedule
transfer_enabledbooleanfalseneeds at least one targetTransfer to a human
transfer_targetslist[]up to 5 {name, number, description}Transfer to a human
transfer_messagestringPlease hold, I'm connecting you to {target}.up to 300 characters, {variables} and {target} allowedTransfer to a human
transfer_summarybooleantrueTransfer to a human
{
  "call": {
    "record": true,
    "allow_interruptions": true,
    "interruption_min_words": 2,
    "check_user_online": true,
    "user_online_message": "Hello, are you still there?",
    "user_online_after_seconds": 8,
    "hangup_after_silence_seconds": 20,
    "hangup_on_prompt": true,
    "hangup_prompt": "You are an AI assistant determining if a conversation is complete. ...",
    "hangup_interruptible": false,
    "hangup_message": "Thank you {name}, have a great day!",
    "max_duration_seconds": 300,
    "call_start_hour": 10,
    "call_end_hour": 19,
    "timezone": "Asia/Kolkata"
  }
}

The console’s New agent template turns check_user_online on. An agent created through the API without it has the check off.

Interruptions

With allow_interruptions on, the caller can talk over the agent. As soon as the caller has said at least interruption_min_words words while the agent is speaking or preparing a reply, Vaakyo:

  1. stops the reply and clears the audio not yet played;
  2. keeps only the part of the reply the caller heard, marked "interrupted": true in the transcript;
  3. emits an interrupted event;
  4. answers what the caller says.

interruption_min_words stops coughs and short listening words (“haan”, “ok”, “hmm”) from cutting the agent off. Fewer words than that while the agent is speaking are kept in the transcript but not answered: the agent carries on. Raise it to 3 or 4 on noisy lines.

A stop word always interrupts, however short: “stop”, “wait”, “hold on”, “sorry”, “excuse me”, “ruko”, “ruk jao”, “band karo”, “ek minute”, “nahi” (also in Devanagari).

“The part the caller heard” is measured by when the audio actually played, not by what was sent: phone audio reaches Plivo faster than it plays, so a reply cut off halfway keeps only its first half, in the transcript and in what the agent remembers.

With allow_interruptions off, nothing the caller says stops the agent once it has started speaking. What they say meanwhile is kept and answered as soon as the agent finishes. If the caller adds to their turn while the agent is still preparing a reply (before any audio), that reply is dropped and the agent answers everything they said.

Once the agent has decided to hang up (with end_call, the hang-up prompt or on silence), nothing the caller says interrupts the goodbye. Those last words are kept in the transcript but not answered.

User online check

When the caller goes quiet, the agent can ask once whether they are still there.

FieldWhat it does
check_user_onlineTurns the check on.
user_online_messageWhat the agent says. If empty, the check is skipped.
user_online_after_secondsSeconds of silence before asking. Must be less than hangup_after_silence_seconds, or the check never runs.

Silence is counted from the later of: the last time the caller spoke, or the end of the agent’s last audio. The question is asked once per stretch of silence; when the caller speaks again, the check resets. It emits a user.online_check event.

Hang up on silence

If neither side speaks for hangup_after_silence_seconds, the agent ends the call. If there is a hangup_message, the agent says it first. The call ends with hangup_by: "agent" and hangup_reason: "caller was silent".

The timer does not run while the agent is preparing or speaking a reply, or waiting for a tool.

Hang up using a prompt

In the console this is on the Agent tab, under the prompt.

With hangup_on_prompt on, after each reply the LLM writes, Vaakyo asks the agent’s LLM a separate short question: given the transcript so far and hangup_prompt, is the conversation complete? The answer is structured ({"complete": true} or false), so your prompt only has to describe when a call is over; it doesn’t need to ask for a “Yes” or “No”. When the answer is true, the agent says the hangup_message (if any) and hangs up. The call ends with hangup_reason: "hang-up prompt: conversation complete" and a hangup.prompt event.

  • The check runs in the background; it does not delay the next reply.
  • If the caller has started talking again by the time the answer comes back, the call goes on.
  • The check uses the agent’s llm.model with temperature 0, and its tokens count in the call’s usage.
  • It does not run after scripted lines (welcome, “are you still there?”) or after a reply that already ended the call.

The default hangup_prompt is:

You are an AI assistant determining if a conversation is complete. A conversation is complete if:

1. The user explicitly says they want to stop (e.g., "That's all," "I'm done," "Goodbye," "thank you").
2. The user seems satisfied, and their goal appears to be achieved.
3. The user's goal appears achieved based on the conversation history, even without explicit confirmation.

If none of these apply, the conversation is not complete.

Write your own when your calls have a different end, for example “complete once the caller has confirmed or declined the appointment, or asked not to be called again”.

The decision is made by an LLM, so it can be wrong. Test it on real calls, tune the prompt, and keep max_duration_seconds as a hard limit.

Letting the caller interrupt the goodbye

With hangup_interruptible off (the default), once the hang-up prompt decides the call is over, the caller can’t talk the goodbye away: the agent finishes the hangup_message and hangs up. Turn it on to let a caller who cuts in with something new (and at least interruption_min_words words) stop the goodbye; the hang-up is cancelled and the conversation goes on. This applies only to prompt hang-ups: silence and maximum-length hang-ups always end the call.

Hang-up message

hangup_message is said just before the agent hangs up on silence or by the hang-up prompt. Leave it empty to hang up without a word. {variables} are filled per call, like in the welcome message: "Thank you {name}, have a great day!".

It is not said when the LLM calls end_call (the LLM says its own goodbye in the same reply), when the call hits its maximum length or runs out of credit, or when the caller hangs up.

Maximum length

max_duration_seconds is the longest a call may last, counted from the moment the agent goes live. When it is reached, the call ends at once with hangup_reason: "maximum call length reached".

The limit is lowered for a call when your credits would run out sooner. In that case, the call ends with hangup_reason: "credits ran out" and an error event. See Limits and billing.

Calling hours

Outbound calls are dialled only when the current hour in timezone is at least call_start_hour and less than call_end_hour. With the defaults, calls go out from 9:00 to 20:59 India time.

  • Calls placed outside the window wait in the queue until it opens (and expire after 24 hours).
  • ignore_call_window: true on the place-call request skips the check for that call.
  • Inbound and browser calls ignore calling hours.
  • The window can’t wrap past midnight. If call_start_hour is not less than call_end_hour, outbound calls are never dialled.
  • timezone is an IANA name such as Asia/Kolkata, Asia/Dubai or Europe/London. It also sets the date and time the agent is told at the start of each call.

See Call flow.

Recording

With record on, Vaakyo saves a stereo WAV of the call on its server: the caller on the left channel, the agent on the right. The agent’s audio is placed where the caller actually heard it, and audio cut off by an interruption is removed.

  • Phone calls are recorded at 8 kHz, browser calls at 16 kHz.
  • Calls shorter than half a second are not saved.
  • The call’s recording_seconds gives the length. Download it with GET /api/v1/calls/{id}/recording (add ?download=true for a file name).
curl -o call.wav "https://api.vaakyo.com/api/v1/calls/$CALL_ID/recording?download=true" \
  -H "X-API-Key: $VAAKYO_API_KEY"

A call without a recording returns 404 no recording for this call.

Ambient noise

ambient_sound plays a quiet background sound under the agent’s voice and through its pauses, so the call sounds like it comes from a real place. Choose a preset or upload your own clip; ambient_volume (0 to 100) sets how loud it is. "" (the default) or volume 0 plays nothing.

ambient_soundSound
officeair conditioning, distant voices, occasional typing
call_centremany quiet voices and keyboards
cafemurmur of a busy room, cups and spoons
streettraffic rumble, passing cars, wind
upload:<id>your workspace’s own clip
  • The sound loops seamlessly and keeps playing while the agent is silent. It never covers the agent: at volume 100 it sits about 10 to 15 dB under a voice, and it is turned down a little more while the agent talks.
  • It adds no delay to the agent’s replies (it is mixed into the same audio), and it is in the call’s recording.
  • It works on phone calls and browser test calls.
  • The presets are generated by Vaakyo (no recordings of real places or people) and released under CC0.

Manage clips with the ambient endpoints (permission agents.view to list and preview, agents.edit to upload and delete):

RequestDoes
GET /api/v1/ambientthe presets, then your uploads: [{"id": "office", "name": "Office", "seconds": 10, "kind": "preset"}, {"id": "upload:3f2a...", "name": "rain.wav", "seconds": 12.5, "kind": "upload"}]
POST /api/v1/ambientupload a clip (multipart field file): a PCM WAV file, at most 2 MB and 60 seconds. It is stored as 16 kHz mono. MP3 is not accepted yet.
GET /api/v1/ambient/{id}/audiothe clip as a WAV, to preview it
DELETE /api/v1/ambient/{id}delete an upload; 409 while an agent uses it
curl -X POST https://api.vaakyo.com/api/v1/ambient \
  -H "X-API-Key: $VAAKYO_API_KEY" -F "file=@rain.wav"

An agent can only use its own workspace’s uploads (422 that ambient sound is not in this workspace otherwise).

Noise cancellation

With noise_cancellation on, the caller’s audio is cleaned before it reaches speech to text: steady background noise (a fan, traffic hum, line hiss, a busy room) is turned down, so the transcriber hears the caller more clearly and background noise is less likely to be taken for speech or an interruption. noise_cancellation_strength (0 to 100) sets how hard: higher removes more noise but can make a quiet voice sound thinner. 50 suits most calls; 0 does nothing.

  • It learns the noise from the pauses in the caller’s speech (the quietest sound over the last 2 seconds), so it adapts within a couple of seconds when the noise changes. Sudden sounds (a door, a dog) are not removed.
  • It adds 10 ms of delay and very little work: about 0.1 to 0.2 ms per 20 ms of audio on one CPU core. On a test signal it lowers steady noise by about 10 dB at strength 70 while the voice stays within 0.3 dB.
  • The recording keeps the caller’s original audio.
  • The transcribers are already trained on noisy phone audio; noise cancellation helps most on very noisy lines.

Voicemail detection

With voicemail_detection on, outbound phone calls ask Plivo to detect an answering machine. The call connects at once and the agent starts as usual; Plivo listens for voicemail_detection_seconds and reports what answered.

  • A person: nothing changes. The call’s answered_by is human.
  • A machine: answered_by is machine and a call.voicemail event is added. With a voicemail_message, the agent stops what it was saying, speaks the message (its {variables} filled like the welcome message) and hangs up; without one it hangs up at once. Either way the call ends completed with hangup_reason voicemail.
  • Unsure: answered_by is unknown and the call goes on.
{
  "call": {
    "voicemail_detection": true,
    "voicemail_detection_seconds": 5,
    "voicemail_message": "Hi {name}, this is Asha from Acme Clinics about your appointment. Please call us back on 080 4567 8900."
  }
}
  • Plivo reports a machine as soon as it hears one, often during the greeting, so the start of the message can overlap the greeting.
  • Detection runs only on outbound phone calls; inbound and browser calls ignore it. Plivo may charge for it.
  • In a batch, add voicemail to retry.outcomes to call again contacts whose machine answered.

Keypad input

With dtmf_input on, keys the caller presses on their phone reach the agent as if the caller had said them: [keypad: 1 2 3 #]. Keys pressed less than dtmf_wait_seconds apart arrive as one message; # sends what was typed at once. Pressing a key while the agent is talking interrupts it, however short the entry (with allow_interruptions off it is answered once the agent finishes). The agent’s prompt is told the format, so you can ask for “your 6-digit booking number, then press hash”.

  • Works on phone calls, and in the browser test call (its keypad).
  • Each message is a call.dtmf event with the digits pressed.
  • With dtmf_input off, keys are ignored.

Auto reschedule

With auto_reschedule on, a caller who asks to be called back later (“I’m driving, call me tomorrow at 5”) gets a new call booked for that time.

  1. In the call. The agent has a built-in reschedule_call tool and its prompt is told the current local time, the timezone and the allowed window. It passes the time as a local date-time (2026-10-04T17:00, in the agent’s timezone). The time must be at least 5 minutes ahead, at most reschedule_max_days days ahead, and inside the calling hours; otherwise the tool tells the agent what is allowed and the agent asks for another time. Once it is booked, the agent confirms the day and time, says goodbye and the call ends (hangup_reason: "call rescheduled"), even if the agent forgets to call end_call.
  2. After the call. If the agent did not book one, a short check of the transcript (one request to the agent’s LLM, only with auto_reschedule on and only for phone calls that connected) asks whether the caller asked for a call back at a specific time; if so, and the time follows the same rules, it is booked.

A call books at most one call back: the tool wins, and the after-call check never adds a second.

The call back is an ordinary queued outbound call with the same agent, the same numbers (from the agent’s number to the customer’s), the same user_data and a scheduled_for time. It waits in the queue, without taking a call slot, until that time, then is dialled like any queued call (credits and free slots are checked then). The two calls link to each other:

FieldOnMeaning
rescheduled_to, rescheduled_forthe call where it was askedThe booked call’s id, and when it will ring.
scheduled_for, rescheduled_fromthe booked callWhen it rings, and the call where it was asked.
  • Cancel a booked call like any queued call: POST /api/v1/calls/{call_id}/hangup (or Cancel in the console).
  • Each booking adds a call.rescheduled event to the original call, with source tool or post_call.
  • In a batch, the call back belongs to the same batch and contact (the next attempt). The contact is rescheduled until the call back ends, and that call’s outcome is the contact’s outcome; it does not hold one of the batch’s concurrent calls meanwhile. Pausing or canceling the batch cancels its booked calls (a paused batch’s contacts go back to be called again).
  • In a browser test call the agent can use the tool, but nothing is booked.

Transfer to a human

With transfer_enabled on, the agent can hand a phone call to a person: a support desk, a sales line, a manager. You list up to five transfer_targets, each with a name, a number in E.164 (+919876543210) and a description of when to use it.

{
  "call": {
    "transfer_enabled": true,
    "transfer_targets": [
      {"name": "Support desk", "number": "+919876543210", "description": "billing questions and refunds"},
      {"name": "Sales", "number": "+918045671234", "description": "new orders and pricing"}
    ],
    "transfer_message": "Please hold {name}, I'm connecting you to {target}.",
    "transfer_summary": true
  }
}

How it works:

  1. The agent has a built-in transfer_call tool (arguments: target, one of your target names, and reason, one sentence) and its prompt lists each target with its description. It transfers when the caller needs what a target handles, or asks for a person.
  2. The agent says transfer_message ({target} is the target’s name; {variables} are filled as in the welcome message), then the call is connected to the target’s number. The person sees the agent’s phone number as the caller id. The caller hears ringing.
  3. With transfer_summary on, the person who answers first hears one short sentence, for example “Transferred call from Front desk: a caller on 9 1 9 8 … Reason: Wants a refund on order 42.”, then is connected. It is built from the call and the agent’s reason, without an extra LLM request.
  4. The agent’s part of the call ends: hangup_by: "agent", hangup_reason: "transferred to Support desk", and the call gets transferred_to ({name, number}), transferred_at and transfer_reason. The caller and the person keep talking.
  5. transfer_status says how it went: connecting, then answered, no-answer (rang for 30 seconds), busy, failed or canceled (the caller hung up while it rang). When no one answers, the caller hears “Sorry, no one is available to take your call right now. We’ll get back to you soon. Goodbye.” and the call ends. transfer_seconds is how long the person talked.

Billing: the agent’s part is charged as usual when it ends. The minutes with the person are charged when that leg ends, at your phone-minute (telephony) rate, with no platform fee: the call’s cost_paise grows by transfer_charged_paise, and cost_breakdown.lines gains a telephony line with model: "transfer". The connected leg is capped at what your credits cover at that rate when it starts (at most 4 hours).

  • Phone calls only. Browser test calls don’t offer the tool at all, so trying the agent in the console never transfers; place a phone call to test it.
  • Each transfer adds a call.transferred event (name, number, reason), then a call.transfer_status event (status). Webhook endpoints can subscribe to both. call.completed is sent when the agent’s part ends, so its record shows transfer_status: "connecting"; use call.transfer_status to learn whether the person answered.
  • In a batch, a transferred call counts as completed with outcome: "transferred", and is never retried.
  • Turning transfers on with no targets is refused (422), as is a number not in E.164, more than five targets, or two targets with the same name. transfer_call can’t be the name of your own tool.
  • If the carrier refuses the transfer, the agent says it couldn’t connect the caller and the conversation goes on (an error event says why).
Esc