---
title: "Validate and update a hosted agent's STT, model, and TTS selections"
method: PATCH
path: "/v1/agents/{agent_id}/voice-stack"
---

# Validate and update a hosted agent's STT, model, and TTS selections

`PATCH /v1/agents/{agent_id}/voice-stack`

## Request

- Base URL: `https://api.skysay.ai`
- URL: `https://api.skysay.ai/v1/agents/{agent_id}/voice-stack`
- Auth: none declared

The target agent defines the authenticated workspace. Select platform-managed components by provider/model/voice fields, or bind a component to an already-created hosted-AI credential by its ID in component_credential_bindings. The binding is a reference only: upload a BYOK secret through the separate write-only hosted-ai-credentials API; this route never accepts or returns secret material. Supplying languages and optional sparse language_profiles is an explicit publication: all effective profiles are constructible and credential-owned before an immutable version is stored for admission. `audio_environment` is a separately version-pinned outgoing-only ambience track, shared across language profiles and independent of caller STT; its optional runtime failure never replaces the selected voice/language or prevents the core call. Draft profile edits never alter existing calls or published sessions.

## Path parameters

- `agent_id` string, required

## Request body

- AgentVoiceStackUpdate: Selection/update payload. It carries only selectors, existing credential IDs, tools, and tuning -- never provider secret material. Supplying languages replaces the selected set; supplying language_profiles replaces sparse overrides, while omission preserves them. After language opt-in, shared prompt, stack, or credential saves explicitly republish inherited effective profiles.
  - `answer_detection` AgentAnswerDetection: What the platform does when a call the carrier reported answered turns out not to be a person: non-human audio such as a caller tune, or a voicemail box. Unlike the other tuning sections, a value you send here is STORED even when it equals the current default, and only an ABSENT `answer_detection` key means "follow the platform default". That is deliberate: the platform default for this setting is expected to change once the detector has been measured, and an agent that explicitly chose a value keeps it when that happens. Applied when a call starts; saving never changes an in-progress call.
    - `non_human_audio` 'shadow' | 'end_call' | 'off': `shadow` records what was heard on every answered call and changes nothing the caller experiences: the classification and its detail appear on the call, and no call is ever ended by it. `end_call` acts on the verdict: the agent stops speaking and the call is ended with `ended_reason: no_human_after_answer`, and the classification and its detail appear on the call as they do under `shadow`. `off` collects nothing, so such a call carries no classification at all. `shadow` is the platform default, so it is what an agent with no stored value gets; sending it explicitly stores it, which is how you keep it if that default later moves. This property carries no JSON Schema `default` on purpose, unlike the other tuning properties: a generated client that pre-filled one would send `shadow` on every save and pin a choice the customer never made, which is the one outcome the storage rule above exists to prevent.
    - `voicemail` 'shadow' | 'hang_up' | 'leave_message' | 'off': What the AI agent does when a voicemail box answers an outbound call. Voicemail is judged from the first seconds of audio after the answer; the phone network does not report it. Only mailbox-specific evidence counts as proof: a record beep that follows a spoken greeting, or a mailbox instruction such as "leave a message", from a language whose phrase list is verified (en; English is checked on every call). The beep works in every language. The greeting before it must reach three recognised words, counted by the spaces and punctuation between them, so in Chinese, Japanese, Thai and other scripts written without spaces a short greeting is undercounted, and a beep after it only raises suspicion. Candidate phrase lists (de, es, et, fr, ru) are not yet verified: what they recognise is shown as evidence and never proves a mailbox. In any other language only the beep or an English instruction can. A fast answer, a long greeting or music only raise suspicion, and a call the platform is unsure about is treated as a person. `shadow` (watch only) records the verdict and what the platform would have done on the call in Calls, and changes nothing the caller experiences. `hang_up` ends the call without a message on proof of a mailbox. `leave_message` plays the `voicemail_messages` entry for the call's language after the beep, once per contact, and hangs up without a message when there is none for that language. `off` detects nothing. `hang_up` and `leave_message` are accepted and stored now but currently run as `shadow`: they start acting only after the detector has passed its accuracy check, and each call's evidence shows both the chosen and the effective mode. `shadow` is the platform default; like `non_human_audio`, an explicitly sent value is stored even when it equals the default, and this property carries no JSON Schema `default`. The `answer_detection` object is replaced as a whole on save, so send every key you want to keep.
    - `voicemail_messages` object: The messages left when `voicemail` is `leave_message`, one per language, keyed by language code (`{"en": "...", "et": "..."}`); a locale such as `de-DE` is stored as `de`. `leave_message` requires at least one. The message played is the one for the language the call is actually in; a call in a language with no message is ended without one, never given a message in another language. Plain text; runs of whitespace are collapsed, a blank text removes that language's message, and control characters are refused. Each message is played exactly as approved, never improvised by the AI agent, so keep it neutral: who is calling and how to call back, with no account, amount or other personal details, because a voicemail box can be heard by someone other than the person you meant to reach. Messages are kept when `voicemail` changes to another mode and for languages the agent no longer offers.
  - `audio_environment` AudioEnvironmentSelection
    - `asset_version` 'v1', required: Immutable packaged asset version; it is never inferred.
    - `preset_id` string, required: Exact catalogue id. `off` has no asset.
    - `volume` integer, required: Whole-number selection. Off requires 0; enabled presets require 1 through 100.
  - `begin_message` string: Legacy alias for first_message.
  - `component_credential_bindings` object: Optional stt/llm/tts map to existing hosted-AI credential IDs (or null to use platform-managed selection). References are validated; secret upload is a separate write-only API.
  - `expected_tools` AgentTool[]: Optional previously read complete tools array, including read-only refs. With a tools replacement, a mismatch returns 409 with error AgentToolsChangedError and writes nothing. Workspace editors and calendar attachment send it to avoid lost changes.
    - union: A legacy string placeholder, a built-in tool, a signed webhook tool, or an attached Integration Action. The names `press_digits`, `wait_on_hold` and `abandon_navigation` are reserved for phone-menu navigation and refused with 400.
      - string: Legacy tool placeholder.
      - BuiltinAgentTool: A built-in tool the AI agent can invoke during a call. `phone_number` is conditionally required: see its own description.
        - `connection_id` string: Required for calendar builtins: one active, owner-configured calendar connection in the AI agent's organization/project. The model cannot select it.
        - `country` string
        - `name` 'end_call' | 'send_sms' | 'transfer_to_human' | 'request_callback' | 'set_call_disposition' | 'calendar_check_availability' | 'calendar_book_appointment', required
        - `phone_number` string: Destination for `transfer_to_human`, and required when that tool is enabled. Must be full E.164 including the leading `+` and country code (for example `+12025550100`); a blank, partial, or non-dialable number is rejected when the agent is created or updated, rather than failing mid-call. Ignored by `end_call` and `send_sms`.
        - `ref` string: Read-only, server-issued. A stable opaque identifier for this configured tool, used to name the tool on a call's transcript rows without storing the tool's own name with them. Ignored if you send it: the value is taken from what is already stored, so it cannot be chosen. It survives any edit that keeps the tool's `name` and `type`; renaming, retyping or removing a tool retires its identifier, and transcript rows recorded before that show the tool's kind instead of a name.
        - `type` 'builtin', required
      - WebhookAgentTool
        - `description` string
        - `name` string, required
        - `parameters` object
        - `ref` string: Read-only, server-issued. A stable opaque identifier for this configured tool, used to name the tool on a call's transcript rows without storing the tool's own name with them. Ignored if you send it: the value is taken from what is already stored, so it cannot be chosen. It survives any edit that keeps the tool's `name` and `type`; renaming, retyping or removing a tool retires its identifier, and transcript rows recorded before that show the tool's kind instead of a name.
        - `type` 'webhook', required
        - `url` string, uri, required
      - IntegrationActionAttachment: Explicitly attaches one existing Integration Action to this agent. The model cannot choose another action or endpoint.
        - `connection_id` string, required
        - `name` string, required
        - `ref` string: Read-only, server-issued. A stable opaque identifier for this configured tool, used to name the tool on a call's transcript rows without storing the tool's own name with them. Ignored if you send it: the value is taken from what is already stored, so it cannot be chosen. It survives any edit that keeps the tool's `name` and `type`; renaming, retyping or removing a tool retires its identifier, and transcript rows recorded before that show the tool's kind instead of a name.
        - `tool_id` string, required
        - `type` 'connection', required
  - `first_message` string
  - `follow_caller` AgentFollowCaller: Explicit saved and published automatic multilingual policy. Existing clients may send only enabled; omitted scope means selected. With scope all, the server resolves every language compatible with the exact stack at publication and freezes that concrete set. With selected, languages supplies the allowed subset. Clients cannot supply runtime adapter options, bindings, or rates.
    - `enabled` boolean, required
    - `scope` 'selected' | 'all': Valid only when enabled. Omit for selected compatibility. All is expanded only at explicit publication and can be unavailable when a provider candidate limit is smaller than the compatible set.
  - `language` string
  - `language_profile_revision` integer: Optional optimistic-concurrency revision returned by GET. A stale value returns conflict; reload before retrying.
  - `language_profiles` AgentLanguageProfileOverride[]: When supplied this replaces the sparse override set; omit it to preserve overrides. An engine without a language axis (see voice_frontend and the agent-catalog conversation_engines block) REFUSES this field with 400 rather than accepting and dropping it.
    - `greeting` string
    - `locale` string, required
    - `transcriber` object
      - `model` string, required
      - `provider` string, required
    - `voice` object
      - `model` string, required
      - `provider` string, required
      - `voice_id` string, required
  - `languages` string[]: When supplied this replaces the selected language set. An engine without a language axis (see voice_frontend and the agent-catalog conversation_engines block) REFUSES this field with 400 rather than accepting and dropping it.
  - `model` VoiceComponentSelection
    - `language` string: STT selection language when supplied.
    - `model` string, required
    - `provider` string, required
    - `voice_id` string: TTS voice identifier when supplied.
  - `navigation` AgentNavigation: Whether the AI agent answers automated phone menus and waits in hold queues on the calls it places, and for how long. On by default: an agent that never chose (`enabled` null) navigates, and a customer who does not want it turns it off once with `enabled: false`, which every save keeps. A write of this section must say what `enabled` is: `true` (on), `false` (off) or `null` (follow the platform default, which is on). An empty object, `null` for the whole section, or a section without `enabled` is refused with 400 `navigation_enabled_required` (an older client used `{}` for off, and it is never guessed at). The section is replaced whole: limits you leave out return to their defaults. Agent create accepts the same section as `metadata.navigation` under the same rules; the generic agent update keeps the stored section and refuses a different one, naming this route. Navigation applies only to outbound calls (calls the agent places, campaign calls included); inbound calls (a phone test you dial in to included), browser tests, simulations, chat and SMS never navigate. Time in menus and on hold is billed at the normal call rate, and a campaign retries a call that ended at the hold limit or the call's maximum length, or whose other side hung up during the hold, without a person reached earlier (running out of the menu limit is final), so a waiting call can cost far more than one that ends at once. While a call is in a menu or on hold, the answer-detection non-human-audio ending is suspended; turn navigation off if an agent must hang up on hold music. The two limits are per agent and apply to the whole call, never to one menu or one queue: time spent in menus and time spent on hold are counted separately, and the call also ends if it reaches its overall maximum length (`turn_taking.max_call_seconds`). A single call cannot change them. While navigation is on (chosen or by default), a limit longer than the AI agent's maximum call length (`turn_taking.max_call_seconds`, at most 1740 seconds) is refused with 400, because a hold or a menu cannot outlast the call; the same refusal applies when you shorten the maximum call length below a limit already set, and when you switch navigation back on beside such a limit. A limit kept beside `enabled: false` is kept as it is. Applied when a call starts; saving never changes an in-progress call, and no republish is needed. Saving and publishing are never refused because an agent cannot navigate yet: an agent on GPT-Live, or one whose languages navigation is not released in (it is released in English, German, French, Spanish, Italian, Dutch, Portuguese, Russian and Polish; an agent qualifies when at least one of its languages is released and the transcribers Skysay supports can listen to all of its languages with at most two connections; its menus in the unreleased ones are then not navigated), keeps its setting, its calls run without navigation, and the voice-stack response says why in `navigation.eligible` and `navigation.ineligible_reason`. Such agents start navigating when support ships, with no change on your side. The tool names `press_digits`, `wait_on_hold` and `abandon_navigation` are reserved for navigation and refused for your own tools.
    - `effective` boolean: Read-only (see AgentNavigationStatus). Accepted and ignored on write, so a section read back from GET can be sent as it is.
    - `eligible` boolean: Read-only (see AgentNavigationStatus). Accepted and ignored on write.
    - `enabled` boolean, nullable, required: `true` turns phone-menu navigation and hold waiting on for this agent's outbound calls, `false` turns it off and is kept by every save, and `null` follows the platform default (on). Required in every write of this section.
    - `ineligible_reason` string, nullable: Read-only (see AgentNavigationStatus). Accepted and ignored on write.
    - `max_hold_seconds` integer: The most time a call may spend waiting in a hold queue before it ends. When it runs out the call ends with `ended_reason` `ivr_timeout` and `reason_detail` `hold_limit`.
    - `max_navigation_seconds` integer: The most time a call may spend answering phone menus before it ends. When it runs out the call ends with `ended_reason` `ivr_timeout` and `reason_detail` `navigation_limit`.
  - `opening` AgentOpening: Outbound opening policy shared across conversation engines. Only managed outbound telephone calls use this wait; inbound and browser tests retain their normal opening. Saved by voice-stack PATCH and frozen in each call publication. Empty object resets to callee-first with two seconds of observed silence. Speech is allowed to finish beyond this wait. The timer authorizes the opening request, not first audible sound; provider generation and network latency add time.
    - `first_speaker` 'callee' | 'agent'
    - `silence_wait_seconds` number: Silence before requesting the first message when first_speaker is callee. Preserved but unused with agent-first.
  - `system_prompt` string
  - `tools` AgentTool[]
    - union: A legacy string placeholder, a built-in tool, a signed webhook tool, or an attached Integration Action. The names `press_digits`, `wait_on_hold` and `abandon_navigation` are reserved for phone-menu navigation and refused with 400.
      - string: Legacy tool placeholder.
      - BuiltinAgentTool: A built-in tool the AI agent can invoke during a call. `phone_number` is conditionally required: see its own description.
        - `connection_id` string: Required for calendar builtins: one active, owner-configured calendar connection in the AI agent's organization/project. The model cannot select it.
        - `country` string
        - `name` 'end_call' | 'send_sms' | 'transfer_to_human' | 'request_callback' | 'set_call_disposition' | 'calendar_check_availability' | 'calendar_book_appointment', required
        - `phone_number` string: Destination for `transfer_to_human`, and required when that tool is enabled. Must be full E.164 including the leading `+` and country code (for example `+12025550100`); a blank, partial, or non-dialable number is rejected when the agent is created or updated, rather than failing mid-call. Ignored by `end_call` and `send_sms`.
        - `ref` string: Read-only, server-issued. A stable opaque identifier for this configured tool, used to name the tool on a call's transcript rows without storing the tool's own name with them. Ignored if you send it: the value is taken from what is already stored, so it cannot be chosen. It survives any edit that keeps the tool's `name` and `type`; renaming, retyping or removing a tool retires its identifier, and transcript rows recorded before that show the tool's kind instead of a name.
        - `type` 'builtin', required
      - WebhookAgentTool
        - `description` string
        - `name` string, required
        - `parameters` object
        - `ref` string: Read-only, server-issued. A stable opaque identifier for this configured tool, used to name the tool on a call's transcript rows without storing the tool's own name with them. Ignored if you send it: the value is taken from what is already stored, so it cannot be chosen. It survives any edit that keeps the tool's `name` and `type`; renaming, retyping or removing a tool retires its identifier, and transcript rows recorded before that show the tool's kind instead of a name.
        - `type` 'webhook', required
        - `url` string, uri, required
      - IntegrationActionAttachment: Explicitly attaches one existing Integration Action to this agent. The model cannot choose another action or endpoint.
        - `connection_id` string, required
        - `name` string, required
        - `ref` string: Read-only, server-issued. A stable opaque identifier for this configured tool, used to name the tool on a call's transcript rows without storing the tool's own name with them. Ignored if you send it: the value is taken from what is already stored, so it cannot be chosen. It survives any edit that keeps the tool's `name` and `type`; renaming, retyping or removing a tool retires its identifier, and transcript rows recorded before that show the tool's kind instead of a name.
        - `tool_id` string, required
        - `type` 'connection', required
  - `transcriber` VoiceComponentSelection
    - `language` string: STT selection language when supplied.
    - `model` string, required
    - `provider` string, required
    - `voice_id` string: TTS voice identifier when supplied.
  - `turn_taking` AgentTurnTaking: Per-agent barge-in and endpointing tuning. Every field is optional and omitted from responses when it is at its default, so an agent that has never configured this section has no `turn_taking` key at all. Applied when a call starts; saving never changes an in-progress call.
    - `barge_in_speech_rms` number: Deprecated. Accepted, stored and returned for compatibility only; this runtime does not apply it to call behavior. Use `interruption_sensitivity`, `min_speech_ms` and `min_interruption_words` to control interruptions.
    - `interruption_sensitivity` 'balanced' | 'low' | 'high': Hosted phone calls in every language. Preset interruption threshold: balanced 500 ms (the default), low 750 ms, high 150 ms. Preset end-of-turn hold on a language with the trained turn detector, the wait when the detector judges the caller finished: balanced 600 ms (the default), low 900 ms, high 260 ms; and the longest wait when it judges them probably not finished, before the turn ends anyway: balanced 1200 ms (the default), low 1800 ms, high 600 ms. On a language without the detector, where the hold is the whole end-of-turn decision: balanced 800 ms (the default), low 1100 ms, high 600 ms. Phone lines get these longer holds than a browser test because a caller pausing mid-sentence (after their name, before a number, while leaving a message) must not have their turn ended for them; `low` holds longer still, and `high` replies sooner. An explicit `min_speech_ms` or `silence_hold_ms` takes precedence over the preset for its knob. The end-of-turn hold is a minimum wait, not the reply time: a turn never ends before voice detection reports the end of speech, which takes about 250 ms of silence, or before the final transcript arrives, which depends on the speech-to-text provider.
    - `max_call_seconds` integer: How long a call may run before the platform ends it and reports `ended_reason: max_call_duration_reached`. Defaults to 1740 (29 minutes), which is also the ceiling: the underlying media session is dropped at 30 minutes, and a limit above that could never fire, so the call would end with no reason attached. Lower it to cap a call sooner; a value outside the range is refused rather than clamped.
    - `min_interruption_words` integer: Defaults to 1 on hosted ENGLISH phone calls and to 0 everywhere else; set 0 explicitly to disable it. When set, an interruption must ALSO carry at least this many recognized words from the caller, in addition to the speech-duration threshold; it never lowers that threshold. On hosted ENGLISH calls, short listening noises ("mm-hm", "uh-huh", "yeah", "okay", "right", "sure", and filler words such as "uh") do not count toward it, so a caller showing they are following does not interrupt; every word counts in other languages. Not English-only, but effective only on stacks whose transcriber reports words fast enough to land inside the decision window (today the Deepgram stacks, English and Estonian). Elsewhere it is stored, returned unchanged, and ignored at call time.
    - `min_speech_ms` integer: Hosted phone calls in every language. Explicit continuous-speech threshold before an interruption is considered; it overrides any sensitivity preset. On a phone call the value is honoured down to 150 ms; a value below 150 ms is raised to 150 ms. A phone call with no value set and no sensitivity preset uses the 500 ms default. Browser and web calls do not apply this value at all: it is still stored and returned, but those calls keep the stack's own threshold.
    - `no_caller_speech_seconds` integer: How long after the call is answered the platform waits to hear the other party before ending the call and reporting `ended_reason: no_caller_speech_after_answer`. Defaults to 20. Speech is detected from the audio itself, not from a transcript, and once the other party has been heard once the rule does not apply again for the rest of that call. The floor is 10 because the witness is voice activity: a caller who is merely listening looks the same as one who is not there, and a shorter value ends the call partway through the agent's own opening line.
    - `silence_hold_ms` integer: Hosted phone calls in every language. The shortest wait after the caller stops speaking before their turn can end; it overrides the platform default hold and any sensitivity preset's shortest wait (the preset's longest wait still applies on a language with the trained turn detector, and is raised to this value when it is shorter). It is a minimum, not the reply time: a value below about 250 ms has no effect, because voice detection needs that much silence to report the end of speech, and the wait is longer whenever the final transcript arrives later than this value.
  - `voice` union
    - VoiceComponentSelection
      - `language` string: STT selection language when supplied.
      - `model` string, required
      - `provider` string, required
      - `voice_id` string: TTS voice identifier when supplied.
    - string: Legacy voice label.
  - `voice_frontend` 'cascade' | 'gpt_live' | 'gemini_live': Optional conversation engine. Omitted or cascade is STT/LLM/TTS. gpt_live selects GPT-Live-1. GET still returns transcriber, model, and voice as an OpenAI compatibility projection. Each engine declares whether it has a language axis; a voice-to-voice engine does not, and this route then refuses languages, language_profiles and follow_caller with a 400 naming the field and the engine. Read has_language_axis from the agent-catalog conversation_engines block rather than assuming it from the id.
  - `voice_polish` AgentVoicePolish: Per-agent voice polish. Absent keys keep the platform defaults; the response omits the section entirely when nothing is configured.
    - `acknowledgements` AgentAcknowledgements: Optional short acknowledgement while a slow reply is still being prepared: at most one per caller turn, spoken in the agent's own voice, model and current language on a separate outgoing track, cancelled the moment the real reply starts. Off unless enabled. Without phrases the platform's reviewed list for the call language is used; a language without one stays silent rather than switching to English.
      - `delay_ms` integer: How long the reply may take before an acknowledgement is worth speaking.
      - `enabled` boolean
      - `phrases` string[]: Bounded neutral phrases; digits and currency marks are rejected so a phrase cannot carry a factual, payment, legal or consent claim.
    - `delivery` 'natural' | 'lively' | 'calm' | 'precise': How the selected voice performs its speech. Unset keeps the provider's own defaults, which is what every agent had before this field existed. Each profile maps to allowlisted provider controls only where the selected TTS provider/model truthfully exposes them: ElevenLabs voice settings (eleven_v3 supports stability only), a bounded Gemini TTS synthesis instruction, and a speaking-rate multiplier for Soniox and Cartesia. Fish Audio and Azure neural voices accept the setting but apply no change. The voice preview (POST /v1/voice-previews with the same `delivery`) uses the identical mapping, so an audition sounds like the call.
    - `end_of_call_phrase` string
    - `model_unresponsive` AgentModelUnresponsive: How long the agent may stay silent after the caller has finished speaking before the platform ends the call. It exists because a conversation engine can stop answering without saying so: a provider outage mid-call leaves the line up, the caller talking to nobody and the call billing, and on some engines nothing at all is reported. It counts only time the line was free: the clock starts when a caller turn ends, it does NOT run while the caller is speaking (their own speech is subtracted, so a caller who keeps talking is never cut off mid-sentence, and one who pauses between attempts still reaches the bound), and it is cleared the moment the agent produces anything — a reply, a tool call, or the end of the call. It also never runs while the agent is speaking, while a tool is running, before the caller's first turn, or on a call whose replies come from a workflow. A call ended this way reports `ended_reason` `agent_unresponsive` with `reason_detail` `model_unresponsive` (calls completed before 2026-09-25 reported `model_session_lost` with the same detail). On by default at the platform's own bound; an absent section is that default and is not stored.
      - `seconds` number: The bound, in seconds. `0` switches it off, and is the only value outside the range: an agent that waits on something genuinely slow may need it. Otherwise 10 to 120. The platform's own bound applies when unset.
    - `tool_wait` AgentToolWait: What the caller hears while a slow tool call (an integration action, a knowledge search) is still running. Off unless a mode is set. `sound` plays a quiet keyboard-typing bed on the separate outgoing track once the tool has run for `delay_ms`, cut the instant the tool finishes, capped at 15 seconds; it is wordless, so it works in every language. `acknowledge` speaks one short phrase in the agent's own voice and language instead; without phrases the platform's reviewed list for the call language is used, and a language without one stays silent. Feedback never plays over the caller or the agent, at most once per tool wait, and never for the built-in end-call or transfer tools.
      - `delay_ms` integer: How long a tool may run before feedback starts. Most tools finish well inside this.
      - `mode` 'off' | 'acknowledge' | 'sound'
      - `phrases` string[]: For `acknowledge` only; the same bounded neutral-phrase rule as acknowledgements. Rejected with `sound`.
    - `unrecognised_speech` AgentUnrecognisedSpeech: What the agent does when the caller speaks and the transcriber produces nothing at all — most often a caller using a language the agent's transcriber is not listening for, but also a bad line or a dropout. The agent says one short line in the call's own language telling the caller it could not understand them and which languages it can help in, then keeps listening; it never guesses what was said, never calls a tool and never ends the call. It is spoken only after the caller has stopped talking and the platform's wait for a transcript has expired, so it cannot talk over a live caller, and it is skipped entirely on an agent with follow-the-caller enabled, where the language switch answers instead. On by default, and spoken at most twice per call unless you change `max_per_call`. Send `enabled: false` to switch it off; an absent section is the default and is not stored.
      - `enabled` boolean: `false` switches the line off for this agent, and is the only value stored: the behaviour is on for every agent that says nothing.
      - `max_per_call` integer: How many times one call may hear it. Two when unset.
      - `phrase` string: Your own wording, spoken verbatim in place of the line the agent would otherwise compose in the call's language. Write it in the agent's language: it is not translated.

## Response `200`

Updated safe voice-stack projection

- AgentVoiceStack: Safe read projection. It never includes component credential binding IDs, plaintext/ciphertext, nonce, fingerprint, or provider secrets.
  - `agent_id` string, required
  - `answer_detection` AgentAnswerDetection: What the platform does when a call the carrier reported answered turns out not to be a person: non-human audio such as a caller tune, or a voicemail box. Unlike the other tuning sections, a value you send here is STORED even when it equals the current default, and only an ABSENT `answer_detection` key means "follow the platform default". That is deliberate: the platform default for this setting is expected to change once the detector has been measured, and an agent that explicitly chose a value keeps it when that happens. Applied when a call starts; saving never changes an in-progress call.
    - `non_human_audio` 'shadow' | 'end_call' | 'off': `shadow` records what was heard on every answered call and changes nothing the caller experiences: the classification and its detail appear on the call, and no call is ever ended by it. `end_call` acts on the verdict: the agent stops speaking and the call is ended with `ended_reason: no_human_after_answer`, and the classification and its detail appear on the call as they do under `shadow`. `off` collects nothing, so such a call carries no classification at all. `shadow` is the platform default, so it is what an agent with no stored value gets; sending it explicitly stores it, which is how you keep it if that default later moves. This property carries no JSON Schema `default` on purpose, unlike the other tuning properties: a generated client that pre-filled one would send `shadow` on every save and pin a choice the customer never made, which is the one outcome the storage rule above exists to prevent.
    - `voicemail` 'shadow' | 'hang_up' | 'leave_message' | 'off': What the AI agent does when a voicemail box answers an outbound call. Voicemail is judged from the first seconds of audio after the answer; the phone network does not report it. Only mailbox-specific evidence counts as proof: a record beep that follows a spoken greeting, or a mailbox instruction such as "leave a message", from a language whose phrase list is verified (en; English is checked on every call). The beep works in every language. The greeting before it must reach three recognised words, counted by the spaces and punctuation between them, so in Chinese, Japanese, Thai and other scripts written without spaces a short greeting is undercounted, and a beep after it only raises suspicion. Candidate phrase lists (de, es, et, fr, ru) are not yet verified: what they recognise is shown as evidence and never proves a mailbox. In any other language only the beep or an English instruction can. A fast answer, a long greeting or music only raise suspicion, and a call the platform is unsure about is treated as a person. `shadow` (watch only) records the verdict and what the platform would have done on the call in Calls, and changes nothing the caller experiences. `hang_up` ends the call without a message on proof of a mailbox. `leave_message` plays the `voicemail_messages` entry for the call's language after the beep, once per contact, and hangs up without a message when there is none for that language. `off` detects nothing. `hang_up` and `leave_message` are accepted and stored now but currently run as `shadow`: they start acting only after the detector has passed its accuracy check, and each call's evidence shows both the chosen and the effective mode. `shadow` is the platform default; like `non_human_audio`, an explicitly sent value is stored even when it equals the default, and this property carries no JSON Schema `default`. The `answer_detection` object is replaced as a whole on save, so send every key you want to keep.
    - `voicemail_messages` object: The messages left when `voicemail` is `leave_message`, one per language, keyed by language code (`{"en": "...", "et": "..."}`); a locale such as `de-DE` is stored as `de`. `leave_message` requires at least one. The message played is the one for the language the call is actually in; a call in a language with no message is ended without one, never given a message in another language. Plain text; runs of whitespace are collapsed, a blank text removes that language's message, and control characters are refused. Each message is played exactly as approved, never improvised by the AI agent, so keep it neutral: who is calling and how to call back, with no account, amount or other personal details, because a voicemail box can be heard by someone other than the person you meant to reach. Messages are kept when `voicemail` changes to another mode and for languages the agent no longer offers.
  - `audio_environment` union
    - AudioEnvironmentSelection
      - `asset_version` 'v1', required: Immutable packaged asset version; it is never inferred.
      - `preset_id` string, required: Exact catalogue id. `off` has no asset.
      - `volume` integer, required: Whole-number selection. Off requires 0; enabled presets require 1 through 100.
    - AudioEnvironmentInvalidSelection: A historic malformed saved pin, returned visibly instead of silently becoming Off. Choose a supported selection or Off before publishing again.
      - `asset_version` string, required
      - `preset_id` string, required
      - `status` 'invalid', required
      - `volume` integer, nullable, required
  - `cost` object, required
  - `first_message` string
  - `follow_caller` AgentFollowCaller: Explicit saved and published automatic multilingual policy. Existing clients may send only enabled; omitted scope means selected. With scope all, the server resolves every language compatible with the exact stack at publication and freezes that concrete set. With selected, languages supplies the allowed subset. Clients cannot supply runtime adapter options, bindings, or rates.
    - `enabled` boolean, required
    - `scope` 'selected' | 'all': Valid only when enabled. Omit for selected compatibility. All is expanded only at explicit publication and can be unavailable when a provider candidate limit is smaller than the compatible set.
  - `language` string, required
  - `language_profiles` AgentLanguageProfiles: Safe editor projection. The immutable runtime profile and credential binding IDs are never returned. Published Follow summaries expose only the frozen allowed locale set, opt-in bit, and opening locale for the authenticated test picker.
    - `languages` string[], required
    - `profiles` AgentLanguageProfileOverride[], required
      - `greeting` string
      - `locale` string, required
      - `transcriber` object
        - `model` string, required
        - `provider` string, required
      - `voice` object
        - `model` string, required
        - `provider` string, required
        - `voice_id` string, required
    - `published_at` string, required: ISO 8601 publication time. An empty string with published_version 0 is the legacy-agent sentinel: no language-profile publication exists yet.
    - `published_follow_caller` AgentFollowCaller, required: Explicit saved and published automatic multilingual policy. Existing clients may send only enabled; omitted scope means selected. With scope all, the server resolves every language compatible with the exact stack at publication and freezes that concrete set. With selected, languages supplies the allowed subset. Clients cannot supply runtime adapter options, bindings, or rates.
      - `enabled` boolean, required
      - `scope` 'selected' | 'all': Valid only when enabled. Omit for selected compatibility. All is expanded only at explicit publication and can be unavailable when a provider candidate limit is smaller than the compatible set.
    - `published_languages` string[], required: Locales from the immutable published profile set. This is empty when no publication exists.
    - `published_starting_language` string, required: Frozen Follow opening locale for the published browser-test picker, or an empty string when Follow is not enabled.
    - `published_version` integer, required
    - `revision` integer, required
  - `latency` object, required
  - `model` object, required
  - `navigation` AgentNavigationStatus: The agent's phone-menu navigation as it stands: the stored choice, what it resolves to, both limits, and whether its outbound calls may navigate. Always present on GET. `eligible` describes the agent, not one call: the call's direction, its reservation and the voice worker still decide each call, and each call's own evidence says what happened.
    - `effective` boolean, required: Whether the setting is on: the stored choice, or the platform default (on) when it is null.
    - `eligible` boolean, required: Whether this agent's outbound calls may navigate.
    - `enabled` boolean, nullable, required: The stored choice: `true`, `false`, or `null` when the agent never chose and follows the platform default.
    - `ineligible_reason` 'turned_off' | 'not_released' | 'engine_not_supported' | 'languages_unreleased' | 'languages_uncovered' | 'too_many_sockets' | 'no_language' | 'tool_name_conflict' | 'tool_budget' | 'null', nullable, required: Why `eligible` is false, else null: `turned_off` (`enabled` is false), `not_released` (navigation is switched off platform-wide), `engine_not_supported` (the agent's conversation engine, GPT-Live today, does not navigate yet), `languages_unreleased` (navigation is released in none of the agent's languages, such as an agent that speaks only Estonian or Finnish), `languages_uncovered` (no supported transcriber covers one of the agent's languages), `too_many_sockets` (covering the agent's languages would need a third transcriber connection), `no_language` (the agent has no language), `tool_name_conflict` (one of the agent's own tools uses a reserved navigation tool name), or `tool_budget` (the agent's own tools leave no room under the 32-name tool contract for the three navigation tools).
    - `max_hold_seconds` integer, required: The hold limit, with the default applied.
    - `max_navigation_seconds` integer, required: The menu limit, with the default applied.
  - `opening` AgentOpening: Outbound opening policy shared across conversation engines. Only managed outbound telephone calls use this wait; inbound and browser tests retain their normal opening. Saved by voice-stack PATCH and frozen in each call publication. Empty object resets to callee-first with two seconds of observed silence. Speech is allowed to finish beyond this wait. The timer authorizes the opening request, not first audible sound; provider generation and network latency add time.
    - `first_speaker` 'callee' | 'agent'
    - `silence_wait_seconds` number: Silence before requesting the first message when first_speaker is callee. Preserved but unused with agent-first.
  - `organization_id` string, required
  - `project_id` string, required
  - `selectable` boolean, required
  - `system_prompt` string
  - `transcriber` object, required
  - `turn_taking` AgentTurnTaking: Per-agent barge-in and endpointing tuning. Every field is optional and omitted from responses when it is at its default, so an agent that has never configured this section has no `turn_taking` key at all. Applied when a call starts; saving never changes an in-progress call.
    - `barge_in_speech_rms` number: Deprecated. Accepted, stored and returned for compatibility only; this runtime does not apply it to call behavior. Use `interruption_sensitivity`, `min_speech_ms` and `min_interruption_words` to control interruptions.
    - `interruption_sensitivity` 'balanced' | 'low' | 'high': Hosted phone calls in every language. Preset interruption threshold: balanced 500 ms (the default), low 750 ms, high 150 ms. Preset end-of-turn hold on a language with the trained turn detector, the wait when the detector judges the caller finished: balanced 600 ms (the default), low 900 ms, high 260 ms; and the longest wait when it judges them probably not finished, before the turn ends anyway: balanced 1200 ms (the default), low 1800 ms, high 600 ms. On a language without the detector, where the hold is the whole end-of-turn decision: balanced 800 ms (the default), low 1100 ms, high 600 ms. Phone lines get these longer holds than a browser test because a caller pausing mid-sentence (after their name, before a number, while leaving a message) must not have their turn ended for them; `low` holds longer still, and `high` replies sooner. An explicit `min_speech_ms` or `silence_hold_ms` takes precedence over the preset for its knob. The end-of-turn hold is a minimum wait, not the reply time: a turn never ends before voice detection reports the end of speech, which takes about 250 ms of silence, or before the final transcript arrives, which depends on the speech-to-text provider.
    - `max_call_seconds` integer: How long a call may run before the platform ends it and reports `ended_reason: max_call_duration_reached`. Defaults to 1740 (29 minutes), which is also the ceiling: the underlying media session is dropped at 30 minutes, and a limit above that could never fire, so the call would end with no reason attached. Lower it to cap a call sooner; a value outside the range is refused rather than clamped.
    - `min_interruption_words` integer: Defaults to 1 on hosted ENGLISH phone calls and to 0 everywhere else; set 0 explicitly to disable it. When set, an interruption must ALSO carry at least this many recognized words from the caller, in addition to the speech-duration threshold; it never lowers that threshold. On hosted ENGLISH calls, short listening noises ("mm-hm", "uh-huh", "yeah", "okay", "right", "sure", and filler words such as "uh") do not count toward it, so a caller showing they are following does not interrupt; every word counts in other languages. Not English-only, but effective only on stacks whose transcriber reports words fast enough to land inside the decision window (today the Deepgram stacks, English and Estonian). Elsewhere it is stored, returned unchanged, and ignored at call time.
    - `min_speech_ms` integer: Hosted phone calls in every language. Explicit continuous-speech threshold before an interruption is considered; it overrides any sensitivity preset. On a phone call the value is honoured down to 150 ms; a value below 150 ms is raised to 150 ms. A phone call with no value set and no sensitivity preset uses the 500 ms default. Browser and web calls do not apply this value at all: it is still stored and returned, but those calls keep the stack's own threshold.
    - `no_caller_speech_seconds` integer: How long after the call is answered the platform waits to hear the other party before ending the call and reporting `ended_reason: no_caller_speech_after_answer`. Defaults to 20. Speech is detected from the audio itself, not from a transcript, and once the other party has been heard once the rule does not apply again for the rest of that call. The floor is 10 because the witness is voice activity: a caller who is merely listening looks the same as one who is not there, and a shorter value ends the call partway through the agent's own opening line.
    - `silence_hold_ms` integer: Hosted phone calls in every language. The shortest wait after the caller stops speaking before their turn can end; it overrides the platform default hold and any sensitivity preset's shortest wait (the preset's longest wait still applies on a language with the trained turn detector, and is raised to this value when it is shorter). It is a minimum, not the reply time: a value below about 250 ms has no effect, because voice detection needs that much silence to report the end of speech, and the wait is longer whenever the final transcript arrives later than this value.
  - `voice` object, required: The stored TTS selection as the agent catalog projects it, including `delivery_control` (`voice_settings`, `instruction`, `speed` or `unsupported`): whether a `voice_polish.delivery` profile changes this voice's audio, and `delivery_profiles`: the exact value each profile resolves to on this engine (empty for `unsupported`), both read from the packaged delivery table.
  - `voice_frontend` 'cascade' | 'gpt_live' | 'gemini_live': The conversation engine; cascade is the default. Always present on GET; omitted on older rows means cascade. Whether this engine has a language axis is declared in the agent-catalog conversation_engines block (has_language_axis).
  - `voice_label` string
  - `voice_polish` AgentVoicePolish: Per-agent voice polish. Absent keys keep the platform defaults; the response omits the section entirely when nothing is configured.
    - `acknowledgements` AgentAcknowledgements: Optional short acknowledgement while a slow reply is still being prepared: at most one per caller turn, spoken in the agent's own voice, model and current language on a separate outgoing track, cancelled the moment the real reply starts. Off unless enabled. Without phrases the platform's reviewed list for the call language is used; a language without one stays silent rather than switching to English.
      - `delay_ms` integer: How long the reply may take before an acknowledgement is worth speaking.
      - `enabled` boolean
      - `phrases` string[]: Bounded neutral phrases; digits and currency marks are rejected so a phrase cannot carry a factual, payment, legal or consent claim.
    - `delivery` 'natural' | 'lively' | 'calm' | 'precise': How the selected voice performs its speech. Unset keeps the provider's own defaults, which is what every agent had before this field existed. Each profile maps to allowlisted provider controls only where the selected TTS provider/model truthfully exposes them: ElevenLabs voice settings (eleven_v3 supports stability only), a bounded Gemini TTS synthesis instruction, and a speaking-rate multiplier for Soniox and Cartesia. Fish Audio and Azure neural voices accept the setting but apply no change. The voice preview (POST /v1/voice-previews with the same `delivery`) uses the identical mapping, so an audition sounds like the call.
    - `end_of_call_phrase` string
    - `model_unresponsive` AgentModelUnresponsive: How long the agent may stay silent after the caller has finished speaking before the platform ends the call. It exists because a conversation engine can stop answering without saying so: a provider outage mid-call leaves the line up, the caller talking to nobody and the call billing, and on some engines nothing at all is reported. It counts only time the line was free: the clock starts when a caller turn ends, it does NOT run while the caller is speaking (their own speech is subtracted, so a caller who keeps talking is never cut off mid-sentence, and one who pauses between attempts still reaches the bound), and it is cleared the moment the agent produces anything — a reply, a tool call, or the end of the call. It also never runs while the agent is speaking, while a tool is running, before the caller's first turn, or on a call whose replies come from a workflow. A call ended this way reports `ended_reason` `agent_unresponsive` with `reason_detail` `model_unresponsive` (calls completed before 2026-09-25 reported `model_session_lost` with the same detail). On by default at the platform's own bound; an absent section is that default and is not stored.
      - `seconds` number: The bound, in seconds. `0` switches it off, and is the only value outside the range: an agent that waits on something genuinely slow may need it. Otherwise 10 to 120. The platform's own bound applies when unset.
    - `tool_wait` AgentToolWait: What the caller hears while a slow tool call (an integration action, a knowledge search) is still running. Off unless a mode is set. `sound` plays a quiet keyboard-typing bed on the separate outgoing track once the tool has run for `delay_ms`, cut the instant the tool finishes, capped at 15 seconds; it is wordless, so it works in every language. `acknowledge` speaks one short phrase in the agent's own voice and language instead; without phrases the platform's reviewed list for the call language is used, and a language without one stays silent. Feedback never plays over the caller or the agent, at most once per tool wait, and never for the built-in end-call or transfer tools.
      - `delay_ms` integer: How long a tool may run before feedback starts. Most tools finish well inside this.
      - `mode` 'off' | 'acknowledge' | 'sound'
      - `phrases` string[]: For `acknowledge` only; the same bounded neutral-phrase rule as acknowledgements. Rejected with `sound`.
    - `unrecognised_speech` AgentUnrecognisedSpeech: What the agent does when the caller speaks and the transcriber produces nothing at all — most often a caller using a language the agent's transcriber is not listening for, but also a bad line or a dropout. The agent says one short line in the call's own language telling the caller it could not understand them and which languages it can help in, then keeps listening; it never guesses what was said, never calls a tool and never ends the call. It is spoken only after the caller has stopped talking and the platform's wait for a transcript has expired, so it cannot talk over a live caller, and it is skipped entirely on an agent with follow-the-caller enabled, where the language switch answers instead. On by default, and spoken at most twice per call unless you change `max_per_call`. Send `enabled: false` to switch it off; an absent section is the default and is not stored.
      - `enabled` boolean: `false` switches the line off for this agent, and is the only value stored: the behaviour is on for every agent that says nothing.
      - `max_per_call` integer: How many times one call may hear it. Two when unset.
      - `phrase` string: Your own wording, spoken verbatim in place of the line the agent would otherwise compose in the call's language. Write it in the agent's language: it is not translated.
  - `warnings` string[], required

## Other responses

- `400`: Invalid component selection, credential reference, language, or tuning: {error: 'BadRequestError', message}. When a language of the stack cannot be published, the body also carries language, reason (the first code) and blockers: [{code, scope, detail}] in the admission vocabulary, e.g. reason 'voice_id_missing' when no voice is chosen for that language.
- `404`: Agent not found
- `409`: Concurrent tool-list replacement is refused with error AgentToolsChangedError; reload the AI agent before saving. Other conflicts retain their own error code.

---

[API](https://skmtc.dev/skysay/apis/skysay-api.md) · [All operations](https://skmtc.dev/skysay/apis/skysay-api/llms.txt) · [OpenAPI document](https://skmtc.dev/skysay/apis/skysay-api/revisions/95b8c66f99aa?raw)
