Phone menu navigation
Let an AI agent answer automated phone menus and wait on hold on outbound calls - how to switch it on, what the Calls page shows, how these calls end, when they are retried, and what they cost.
An outbound call to a company often reaches an automated menu ("press 2 for English") and then a hold queue, not a person. With phone menu navigation on, the AI agent listens to each menu, presses the key that fits the call's task (or answers a menu that asks you to speak), waits quietly on hold, and starts talking when a person picks up. It is off by default and you opt in per agent. It works on outbound calls only: support for keypad menus on inbound calls comes later.
A call can last at most 29 minutes, including time in menus and on hold. If nobody answers by then, the call ends as “Hold limit reached” (ended_reason ivr_timeout, reason_detail deadline). No setting raises this limit.
This page is the whole picture in one place. The reference for each part is in Answer detection (the setting, the menu hint and the languages), Call variables (numbers the agent may type) and Post-call results (the ending values). Campaign retries are described in Campaigns.
Which agents can use it
Phone menu navigation works for agents on the Cascade engine, including
agents that run a conversation workflow, and for agents on Gemini Live. It
is not available for agents on GPT-Live at this release: in the workspace the
setting is shown switched off, with the reason beside it. Through the API, turning navigation.enabled on for a
GPT-Live agent, or switching an agent that has it on to GPT-Live, is refused with
400 navigation_engine_not_supported; the response names the engine and lists
the supported ones (supported_engines). Each engine in the agent catalog
(GET /v1/agent-catalog) says whether it can navigate in supports_navigation.
Switch the agent to Cascade or Gemini Live first, or leave navigation off.
Turn it on
In the workspace, open the AI agent, then Tools, and tick Navigate phone menus and wait on hold. Two limits appear under it, with notes on the menu languages and on which voices may say a value (see Languages and Typed and spoken values):
| Setting | API field | Default | Range |
|---|---|---|---|
| Max hold time | navigation.max_hold_seconds | 600 seconds (10 minutes) | 10 seconds up to 29 minutes: a whole call lasts at most 29 minutes, menus and hold included |
| Max time in menus | navigation.max_navigation_seconds | 180 seconds (3 minutes) | 10 seconds up to 15 minutes |
Time in menus and time on hold have separate limits: a long hold never uses up the time allowed for menus, and the other way round. The API schema allows up to 3600 seconds for hold and 900 seconds for menus, but a hold or a menu cannot outlast the call, so the 29-minute call maximum caps both.
Through the API the same setting is the navigation section of
PATCH /v1/agents/{agent_id}/voice-stack:
curl -X PATCH https://api.skysay.ai/v1/agents/agt_123/voice-stack \
-H "Authorization: Bearer $SKYSAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"navigation": {"enabled": true, "max_hold_seconds": 900}}'A hold or a menu cannot outlast the call that contains it, so neither limit can be longer than the agent's maximum call length (turn_taking.max_call_seconds, 1740 seconds = 29 minutes unless you shorten it). A longer limit is refused with 400, and so is shortening the maximum call length below a limit you set.
The limits belong to the agent and are not changed per call. Time in menus and time on hold are counted separately across the whole call, and the call also ends at its overall maximum length. The setting applies when a call starts; saving never changes a call in progress. Navigation only applies to calls the agent places, not to calls it receives.
What the agent does
It has four jobs, and nothing else changes while it is in a menu or a queue:
- Answering menus. It listens to each menu and answers it the way the menu asks. On a keypad menu it presses the key that fits the call's task and listens again. On a menu that asks you to speak ("say billing") it says the answer. While it is in a menu or a queue, silence, hold music or a recorded "goodbye" never hangs the call up.
- Waiting on hold. It stays silent through the whole queue: hold music, announcements, and "press 1 to stay in the queue", which it answers by pressing the key. Only the two limits or the call's maximum length end the wait early.
- Declining callback offers. When a menu offers to call you back instead of holding, the agent declines it automatically, by key or by voice. Silence never accepts a callback either. If the offer has an option to stay in the queue, it takes that one. On Gemini Live, a callback offer that can only be answered by speech gets no spoken decline: the callback is never taken, and the call stays in the queue.
- Giving up on a menu. When no option fits the call's task, the menu asks for something the call does not carry, or the key presses change nothing, it ends the call instead of guessing. The call ends as Menu had no matching option and is not redialled.
When something that may be a person answers, the agent first confirms it with a short check question, such as whether it is speaking with a person. Only then does it start the conversation.
Tell it what to expect. If you know the menu, pass navigation_hint in the
call's call_context or as a campaign column (for example Press 2 for English, then 1 for billing.). It is plain text of at most 500 characters, reaches the
agent as the built-in variable call.navigation_hint, and a longer or non-text
hint refuses the call with call_variables_invalid. It is a hint, not a script:
the agent still answers the menu it actually hears.
Numbers the menu asks for
A menu often asks for a number: a card number, a PIN, a government ID, a date of
birth, an account or booking reference. These come only from the call's own
data, as a keypad variable: you declare the variable
with keypad and a class (card, pin, government_id, date_of_birth,
reference or other) and send its value with the call. The agent never
makes one up, and a menu that asks for a number the call does not carry gets
nothing.
- The model never sees the value. It is not in the prompt, the conversation, a tool request or a webhook. The agent can ask for the field to be entered; the platform fetches the value at that moment and sends it.
- Transcripts show the field, not the digits. A typed value reads as
Entered {field}(for exampleEntered {account_number}) in transcripts, the timeline and logs. - The value is deleted at the end of the call. It is not kept in the call record afterwards, so a retry needs the value sent again with the new call.
Typed and spoken values
Most menus take a number on the keypad. Typed values work on every supported engine, everywhere keys are accepted, except on agents whose languages include Polish (see Languages). Some menus accept a number only by voice ("say your account number"). The platform then speaks the digits itself, in the agent's own voice, without the model.
- Spoken values (a stored value said aloud to a prompt that asks for it to be spoken) are supported on Cascade agents only, workflow agents included, and only with these voices: Azure AI Speech in English, German, French, Spanish, Italian, Dutch, Portuguese and Russian; Gemini TTS in English, German, French, Spanish, Dutch and Russian; ElevenLabs Flash v2.5 in English. With any other voice or language, the value is entered on the keypad only, and a question that can be answered by speech alone ends the call.
- On Gemini Live, a prompt that takes speech only ends the call as
data not authorised (
ended_reasonivr_timeout,reason_detaildata_not_authorised) before the value is read. Prompts that take keys, take either, or don't say how to answer still get the value as keypad tones.
Languages
Phone menu navigation works in English, German, French, Spanish, Italian, Dutch,
Portuguese, Russian and Polish. Estonian and Finnish are not supported. An agent
whose languages are all unsupported cannot turn navigation on (the API answers
400 navigation_languages_unsupported with reason unreleased). An agent with at
least one supported language can, and its menus in other languages are not
navigated.
Agents whose languages include Polish navigate Polish menus, but cannot enter
keypad values yet (value_language_unsupported). That applies to the whole
agent: a typed value is not entered on any call of an agent that has Polish
among its languages, and the menus are still navigated. When such a call reaches
a question that asks for a value it carries, such as a PIN or a card number, the
call ends as data not authorised (ended_reason ivr_timeout,
reason_detail data_not_authorised), because Polish cannot enter keypad values
yet.
To listen to the far end the agent uses at most two speech-to-text connections,
chosen from the transcribers Skysay supports for the agent's selected languages.
If the agent's languages cannot be covered that way, switching navigation on is
refused with 400 navigation_languages_unsupported; the response lists the
language codes concerned in languages, and reason is uncovered (no
supported transcriber hears that language), too_many_sockets (every language is
covered, but not by two connections) or unreleased (see above). Remove a
language, add a supported one, or turn navigation off. Details are in Answer detection.
Lines that carry keys
Pressing keys needs a carrier line where key delivery has been proven. Skysay records this per line and sends keys only on a line that is proven: DIDWW is proven today, and every other line is refused until it is. A customer-owned trunk and, today, the DIDLogic line do not send keys. The agent does not pretend: the timeline says Keys not supported on this line, and a menu that needs keys ends the call as Menu had no matching option. Menus you can answer by speaking, and holds, work on every line.
A self-hosted (open-source) worker does not send keys either. The table that says which carrier lines can carry key presses is not part of the open-source export, so a self-hosted worker refuses key presses and reports Keys not supported on this line. See Deployment modes.
What makes it reliable
Navigation is not left to prompt instructions. It rests on these parts.
- A detector, not prompt instructions. A detector tells a menu, a queue, a recording and a person apart, rather than relying on instructions in the agent's prompt.
- A person check before the agent talks. When something answers, the agent confirms a real person with a short check question, then continues the conversation.
- Carrier-proven key delivery. Keys are sent only on carriers proven to deliver them.
- Sensitive values isolated from the model. Card, PIN, government ID and date of birth come only from the call's own data, and the model never sees them.
- Automatic callback decline. Callback offers are declined automatically, and silence never accepts one.
- Separate time limits for menus and holds. A long queue cannot use up the time allowed for menus, and the other way round, and the whole call is still capped at 29 minutes.
- Tested before every release. Every release is tested against about 40 call scenarios, on each supported engine where the scenario applies.
What you see in the workspace
Calls list. The list shows short chips. A call that went through a menu gets one quiet chip in the Outcome column, for example Menu → hold 6:40. A call that stopped there also gets a short ending chip:
| Chip | Meaning |
|---|---|
| Hold limit | nobody picked up within the max hold time, or the call reached its maximum length while it waited |
| No menu match | the menu had no option that fits the call's task, or asked for a number the call did not carry |
| Menu time limit | the agent used the time allowed for menus without reaching a queue or a person |
| Hung up on hold | the other side hung up while the call was on hold |
| Answered by a machine | the number answered with a recorded message only: no menu, no person |
The Failure reason filter has the first three, under Telephony, with their full names (Hold limit reached, Menu had no matching option, Time limit in phone menus reached).
Call details. The header splits the call's duration into Menu, Hold
and Conversation, and says in one sentence how the call ended. The timeline
shows, in order: each menu the agent detected, each key it sent, a typed value
as Entered {field}, each interval on hold, Keys not supported on this line
where a line cannot carry keys, and the moment a person answered, with the short
check that confirmed them, before the conversation itself.
How these calls end
These endings are reported on the call resource as ended_reason with a
reason_detail. A campaign gives each one a disposition and retries it under the
same per-outcome rules as other calls (see Campaigns):
ended_reason | reason_detail | Campaign disposition | Retried |
|---|---|---|---|
ivr_timeout | hold_limit | no_answer | yes, when no person was reached earlier |
ivr_timeout | deadline | no_answer | yes, when no person was reached earlier |
remote_party_disconnected | during_hold | no_answer | yes, when no person was reached earlier |
ivr_timeout | menu_no_path | connected_completed | no, terminal |
ivr_timeout | navigation_limit | connected_completed | no, terminal |
ivr_timeout | data_not_authorised | connected_completed | no, terminal |
no_human_after_answer | recorded_message_only | no_answer | yes, like any other machine answer |
media_session_lost | navigation_detector_lost | connected_completed | no, terminal |
The retried endings are the ones another attempt can change: the hold queue was too long, the call ran out of time, or the other side hung up before a person came on. The terminal ones are dead ends: the same menu will be there next time. A line that only plays a recorded message shows as Answered by a machine.
media_session_lost with navigation_detector_lost is not a limit or a menu
outcome: it means the call could no longer follow the line, because none of the
sources that listen to the far end delivered anything for 30 seconds, so the
platform ended the call. The Calls list files it under the media failures as
Media session lost. It has no campaign rule of its own: the call connected,
so it takes connected_completed unless the agent or its workflow declared
another outcome, and a campaign does not retry it.
person_reached on the call resource says whether a person was reached earlier
in the call; it is null when the call did not report it, and a missing value
never causes a retry. These calls connected, so the carrier reports them as
completed, and the ending says why nothing came of the call.
Cost
Time in menus and on hold is billed at your normal call rate, as part of one call. Navigation adds no surcharge and there is no separate hold price; the split in the call details is for reading, not for billing.
Limits worth knowing
- A menu that changes its mind, forwards the line elsewhere or asks for something unexpected is answered as heard; the agent ends the call at a menu with no matching option rather than guess.
- How well the agent understands a menu is measured language by language before it is claimed (see Answer detection); a language that works on keys can still have a menu the agent misreads.
- Once a person answers, everything they say is an ordinary conversation, even if they read a number back.
Answer detection
What a call carries about who answered it, what happens when only music or tones are heard, and how to change it per agent.
Voicemail detection
How Skysay tells a voicemail box from a person on an outbound call, which evidence can prove a mailbox in which languages, the four per-agent settings, and what each one does today.