---
title: "Update Draft Config"
method: PATCH
path: "/v1/agent/agents/{agent_id}/config"
tags: ["Agents"]
---

# Update Draft Config

`PATCH /v1/agent/agents/{agent_id}/config`

## Request

- Base URL: `https://api.fish.audio`
- URL: `https://api.fish.audio/v1/agent/agents/{agent_id}/config`
- Auth: HTTP bearer

Patch the draft configuration section by section; omitted sections keep
their value. Changes only affect live sessions after the next publish.
`prompt.system_prompt` is limited to 32000 tokens (422 beyond); keeping it
under 2000 tokens is recommended for latency and cost.
`voice.voice_id` accepts any public voice model id.
`voice.speaking_language` accepts any of the 52 supported ISO 639-1 codes
(the same set the console offers, see the Voice & language docs); anything else is 422. `voice.expressive` (default `true`) enables richer
expressive delivery (emotion steering, laughter and sounds, pauses); off
keeps the standard delivery. The `asr` section configures speech
recognition. `asr.model` picks the model: `deepgram:nova-3` (default),
`elevenlabs:scribe_v2_realtime`, `elevenlabs:scribe_v2_medical`, or
`fish_audio:transcribe-stream-1` (Fish realtime recognition, which
also decides end of turn and whether the caller is interrupting).
Its former id `fish_audio:transcribe-1-pro-realtime` is still accepted
and reads back as the current id.
`asr.multilingual` (default `false`) lets recognition follow callers who
switch language mid-call, off tells the recognizer to expect
`voice.speaking_language` (models other than `deepgram:nova-3` may still
transcribe clear speech in another language). `asr.strict_language`
(default `false`) enforces `voice.speaking_language` whatever
`asr.multilingual` says: speech recognized as another language reaches the
agent as `[unintelligible speech]`. Language is detected per utterance, so
a short or heavily accented phrase in the speaking language can
occasionally be detected as another language and replaced too. Only
enable it when you explicitly need to stop the agent from understanding
other languages.
`asr.keyterms` is a recognition
vocabulary of up to 50 plain terms (brand names, product terms, personal
names), each at most 100 characters with no commas or semicolons. `[]`
clears it and 20-50 focused terms work best. `tool_ids` and
`knowledge_source_ids` replace their attachment lists wholesale and every
id must resolve, else 422. `llm.custom` points the agent at your own
OpenAI-compatible endpoint; mutually exclusive with `llm.model`, cleared
with an explicit null. `llm.fallback` sets what the agent tries after its
model fails: `platform` (default), `manual` (`models` in order, 1 to 3)
or `off`. In `manual`, each model and `primary` (the agent's own model)
take optional `ttft_timeout_ms` (500-30000, default 5000 ms, i.e. 5
seconds, when not set) and `total_timeout_ms` (1000-120000, at least
`ttft_timeout_ms` when both are set, no limit when not set), and the
first model can set
`parallel.kick_in_ms` (300-10000, default 1500) to run alongside the
agent's own model.

The response is the draft as stored, which can differ from the request:
- Every field comes back. Omitted fields keep their value or default.
- `asr.keyterms` and `voice.pronunciation_dictionary_ids` are trimmed
  and de-duplicated in order. `tool_ids` is de-duplicated in order.
- A tool that was archived drops out of `tool_ids` at the next tools
  write.
- `conversation.interruption_ignore_phrases` collapses inner spaces and
  drops case-insensitive duplicates.
- `conversation.timezone`, transfer phone numbers and transfer text
  fields are trimmed.
- `conversation.eagerness` and `conversation.response_wait_ms` move
  together. The wait wins when both are sent, and the tier is derived
  from it.
- `conversation.response_max_wait_ms: 0` reads back as `null`
  (automatic).
- `knowledge_base.enabled` becomes `true` when you attach sources and
  omit it.
- `llm.custom.base_url` drops a trailing `/` and `/chat/completions`.
  `llm.custom.model` is trimmed. Setting `llm.custom` resets `llm.model`
  to the platform default.
- Secrets read back as `has_secret`, and `llm.custom.api_key` as `null`.
- `voice` also returns `keyterms`, `multilingual_asr` and `asr_model`
  copied from `asr`. They are deprecated.
- `config_hash` and `updated_at` are set by the server.

Compare against this response, or against `config_hash`, rather than the
request body. Publishing never rewrites these values.

## Path parameters

- `agent_id` string, required

## Request body

- PublicAgentConfigPatchPayload
  - `prompt` PublicAgentPromptPatch
    - `system_prompt` string, nullable: Limited to 32000 tokens (o200k_base); keeping it under 2000 tokens is recommended for latency and cost.
    - `first_message_mode` 'off' | 'fixed' | 'prompt', nullable
    - `first_message` string, nullable
    - `first_message_prompt` string, nullable
  - `voice` PublicAgentVoicePatch
    - `voice_id` string, nullable
    - `speaking_language` 'en' | 'ja' | 'zh' | 'ko' | 'es' | 'fr' | 'de' | 'pt' | 'it' | 'nl' | 'ar' | 'hi' | 'id' | 'tr' | 'ru' | 'bg' | 'sr' | 'hr' | 'cs' | 'sk' | 'pl' | 'uk' | 'ro' | 'hu' | 'el' | 'sv' | 'da' | 'no' | 'fi' | 'et' | 'lv' | 'lt' | 'ca' | 'he' | 'fa' | 'ur' | 'kk' | 'ka' | 'hy' | 'bn' | 'ta' | 'te' | 'kn' | 'mr' | 'gu' | 'pa' | 'ne' | 'th' | 'vi' | 'ms' | 'tl' | 'af', nullable
    - `expressive` boolean, nullable
    - `speed` number, nullable
    - `tts_model` 's2.1-pro' | 'drama-3', nullable
    - `pronunciation_dictionary_ids` string[], nullable
  - `asr` PublicAgentAsrPatch
    - `model` 'deepgram:nova-3' | 'elevenlabs:scribe_v2_realtime' | 'elevenlabs:scribe_v2_medical' | 'fish_audio:transcribe-stream-1', nullable
    - `multilingual` boolean, nullable
    - `strict_language` boolean, nullable: Enforces voice.speaking_language whatever multilingual says: speech recognized as another language reaches the agent as [unintelligible speech]. Language is detected per utterance, so a short or heavily accented phrase in the speaking language can occasionally be detected as another language and replaced too. With deepgram:nova-3, speech in another language is usually not transcribed at all. Only enable it when you explicitly need to stop the agent from understanding other languages.
    - `keyterms` string[], nullable
  - `conversation` PublicAgentConversationPatch
    - `max_duration_seconds` integer, nullable
    - `response_wait_ms` integer, nullable
    - `eagerness` 'relaxed' | 'balanced' | 'eager', nullable
    - `response_max_wait_ms` integer, nullable
    - `interruptible` boolean, nullable
    - `interruption_sensitivity` 'low' | 'balanced' | 'high', nullable
    - `interruption_ignore_phrases` string[], nullable
    - `speculative_response` boolean, nullable
    - `reengage_enabled` boolean, nullable
    - `hangup_after_seconds` integer, nullable
    - `hangup_farewell_mode` 'generated' | 'fixed' | 'off', nullable: What the agent says before a silence hangup. `off` (default) hangs up without a word, `generated` says a short goodbye, `fixed` says hangup_farewell_message. A `fixed` mode with an empty message falls back to `generated`. If the caller speaks during the goodbye, the call goes on.
    - `hangup_farewell_message` string, nullable: The goodbye said verbatim in the `fixed` hangup_farewell_mode, at most 500 characters. `{{variable}}` placeholders are filled in for each session. "" clears it.
    - `record_audio` boolean, nullable
    - `timezone` string, nullable
    - `transfer_destinations` AgentTransferDestinationPatch[], nullable
      - `type` 'phone' | 'sip'
      - `label` string
      - `phone_number` string, nullable
      - `sip_uri` string, nullable
      - `headers` object
      - `description` string
      - `mode` 'cold' | 'warm'
      - `warm_connect` 'confirm' | 'briefing' | 'direct'
      - `warm_briefing_instructions` string
      - `warm_ring_timeout_seconds` integer
      - `warm_confirm_timeout_seconds` integer
      - `warm_human_detection` boolean: Applies to warm transfers whose warm_connect is `confirm` or `briefing`, and is ignored for cold transfers and `direct`. When true, once the destination answers, the agent stays silent until the other side speaks, then classifies the pickup. A person is briefed as usual. Voicemail or an IVR (phone menu) ends the transfer as failed. A hold queue or silence keeps the agent waiting until `warm_confirm_timeout_seconds` ends the attempt as unreachable. A failed transfer follows `on_failure`. Leave it off for destinations whose people are only reachable through a phone menu, because the menu stops the transfer.
      - `warm_consult_uses_agent_settings` boolean: Compatibility option for warm transfers whose warm_connect is `confirm` or `briefing`, ignored for cold transfers and `direct`. Absent or true (recommended): on the call in which the agent briefs the destination, the agent uses the same turn-taking (response timing: `eagerness`, `response_wait_ms` and `response_max_wait_ms`), interruption (`interruption_sensitivity` and `interruption_ignore_phrases`) and language (`voice.speaking_language`) settings as on the call with the caller. False: that call keeps the older fixed behavior. Destinations that existed before this setting were set to false so their behavior did not change. Either way the destination can always interrupt the agent on that call, whatever `interruptible` is set to. A destination sent without the field keeps the value of the existing destination it replaces (same label, else a renamed one with the same phone number or SIP URI) and is true when it is new. Send true to switch a destination to the agent's settings.
      - `on_failure` AgentTransferOnFailurePatch
        - `action` 'return_to_agent' | 'end_call'
        - `message` string
      - `announcement_mode` 'generated' | 'fixed' | 'off'
      - `announcement_message` string
    - `transfer_refuse_questions` boolean, nullable
    - `outbound` AgentOutboundPatch
      - `amd_enabled` boolean
      - `voicemail` AgentOutboundVoicemailPatch
        - `action` 'hangup' | 'leave_message' | 'leave_prompt_message'
        - `message` string
    - `keypad_menu` AgentKeypadMenuPatch
      - `enabled` boolean
      - `prompt` string
      - `options` AgentKeypadMenuOptionPatch[]
        - `key` string, required
        - `value` string, required
      - `accept_speech` boolean
      - `variable` string
      - `no_input_timeout_seconds` integer
      - `max_attempts` integer
      - `retry_message` string
      - `fallback_value` string
    - `keypad_input` AgentKeypadInputPatch
      - `enabled` boolean
      - `terminator` '#' | '*' | 'none'
      - `inter_digit_timeout_seconds` integer
    - `echo_guard` AgentEchoGuardPatch
      - `web_voice` boolean, nullable: Voice calls in a browser or app (web widget, SDK, test calls). Null uses the default (on).
      - `phone_inbound` boolean, nullable: Inbound phone calls. Null uses the default (on).
      - `phone_outbound` boolean, nullable: Outbound phone calls. Null uses the default (off).
  - `tools` PublicAgentToolsPatch
    - `enabled` boolean, nullable
    - `tool_ids` string[], nullable: Replaces the attached tool set wholesale. Every id must be an existing tool in the agent's workspace, else 422.
    - `system_tools` PublicSystemToolsPatch
      - `hang_up_call` boolean, nullable
      - `hang_up_call_farewell_mode` 'generated' | 'fixed' | 'off', nullable
      - `hang_up_call_farewell_message` string, nullable
      - `hang_up_call_refuse_questions` boolean, nullable
  - `webhooks` PublicAgentWebhooksPatch
    - `conversation_init` PublicConversationInitWebhookPayload
      - `url` string, required
      - `secret` string, nullable: Optional HMAC-SHA256 signing secret (X-Fish-Webhook-Signature). Write-only: reads report `has_secret` instead of the value. Omit it (or send null) to keep the secret already stored for the same url; send an empty string to remove it.
      - `timeout_seconds` number: How long the platform waits for the endpoint before connecting the call without variables.
    - `post_call` PublicPostCallWebhookPayload[], nullable: Up to 5 endpoints, each receiving every post-call event. Replaces the configured list wholesale; a single object is still accepted and is stored as a one-element list, and null (or an empty list) clears every endpoint. URLs must be unique.
      - `url` string, required
      - `secret` string, nullable: Optional HMAC-SHA256 signing secret (X-Fish-Webhook-Signature). Write-only: reads report `has_secret` instead of the value. Omit it (or send null) to keep the secret already stored for the same url; send an empty string to remove it.
  - `knowledge_base` PublicAgentKnowledgeBasePatch
    - `enabled` boolean, nullable: Whether the agent retrieves from its attached sources. Defaults to true when omitted alongside a non-empty knowledge_source_ids; send false explicitly to attach sources without using them yet.
    - `knowledge_source_ids` string[], nullable: Replaces the attached knowledge sources wholesale. Every id must be an existing knowledge source visible to the agent, else 422.
  - `analysis` PublicAgentAnalysisPatch
    - `summary` PublicAgentAnalysisSummaryPatch
      - `enabled` boolean
      - `prompt` string, nullable
      - `language` 'en' | 'ja' | 'zh' | 'ko' | 'es' | 'fr' | 'de' | 'pt' | 'it' | 'nl' | 'ar' | 'hi' | 'id' | 'tr' | 'ru' | 'bg' | 'sr' | 'hr' | 'cs' | 'sk' | 'pl' | 'uk' | 'ro' | 'hu' | 'el' | 'sv' | 'da' | 'no' | 'fi' | 'et' | 'lv' | 'lt' | 'ca' | 'he' | 'fa' | 'ur' | 'kk' | 'ka' | 'hy' | 'bn' | 'ta' | 'te' | 'kn' | 'mr' | 'gu' | 'pa' | 'ne' | 'th' | 'vi' | 'ms' | 'tl' | 'af'
    - `data_fields` PublicAgentAnalysisDataField[], nullable
      - `name` string, required
      - `type` 'boolean' | 'text' | 'number' | 'enum'
      - `description` string
      - `enum_options` string[], nullable
    - `criteria` PublicAgentAnalysisCriterion[], nullable
      - `name` string, required
      - `description` string
  - `guardrails` PublicAgentGuardrailsPatch
    - `focus` boolean, nullable
    - `manipulation` boolean, nullable
    - `pre_tool_speech` boolean, nullable
  - `context` PublicAgentContextPatch
    - `current_time` boolean, nullable
  - `llm` PublicAgentLLMPatch
    - `model` 'google/gemini-3.5-flash-lite' | 'google/gemini-3.6-flash' | 'google/gemini-3.8-flash' | 'google/gemini-3.6-flash-priority' | 'google/gemini-3.8-flash-priority' | 'google/gemini-2.5-flash' | 'google/gemma-4-26b-a4b-it' | 'anthropic/claude-haiku-4.5' | 'anthropic/claude-sonnet-4.6' | 'anthropic/claude-opus-5.5' | 'anthropic/claude-fable-5.1' | 'openai/gpt-5.6-luna' | 'openai/gpt-6-luna' | 'openai/gpt-5' | 'openai/gpt-6.1-sol' | 'openai/gpt-6-astra' | 'openai/gpt-4o' | 'openai/gpt-4.1', nullable
    - `custom` PublicAgentLLMCustomConfig
      - `base_url` string, required
      - `model` string, required
      - `api_key` string, nullable: Sent to your endpoint as the Authorization bearer token; write-only, reads return null. Required when introducing `custom`; omit to keep the stored key when updating.
      - `headers` object, nullable: Extra headers sent on every request to your endpoint, such as `x-vercel-protection-bypass`. Reads return the stored map. Omit to keep the stored headers, `{}` clears them.
    - `fallback` PublicAgentLLMFallback
      - `mode` 'platform' | 'manual' | 'off', required
      - `models` PublicAgentLLMFallbackModel[], nullable: Fallback models in order, 1 to 3. Only with mode `manual`.
        - `model` 'google/gemini-3.5-flash-lite' | 'google/gemini-3.6-flash' | 'google/gemini-3.8-flash' | 'google/gemini-3.6-flash-priority' | 'google/gemini-3.8-flash-priority' | 'google/gemini-2.5-flash' | 'google/gemma-4-26b-a4b-it' | 'anthropic/claude-haiku-4.5' | 'anthropic/claude-sonnet-4.6' | 'anthropic/claude-opus-5.5' | 'anthropic/claude-fable-5.1' | 'openai/gpt-5.6-luna' | 'openai/gpt-6-luna' | 'openai/gpt-5' | 'openai/gpt-6.1-sol' | 'openai/gpt-6-astra' | 'openai/gpt-4o' | 'openai/gpt-4.1', nullable
        - `custom` PublicAgentLLMCustomConfig
          - `base_url` string, required
          - `model` string, required
          - `api_key` string, nullable: Sent to your endpoint as the Authorization bearer token; write-only, reads return null. Required when introducing `custom`; omit to keep the stored key when updating.
          - `headers` object, nullable: Extra headers sent on every request to your endpoint, such as `x-vercel-protection-bypass`. Reads return the stored map. Omit to keep the stored headers, `{}` clears them.
        - `ttft_timeout_ms` integer, nullable: Milliseconds the model gets to start its reply (first text or tool call). Past it, the reply moves to the next fallback model. Not set: defaults to 5000 ms (5 seconds).
        - `total_timeout_ms` integer, nullable: Milliseconds the model gets to finish its reply. Past it, a reply nothing of which was spoken yet moves to the next fallback model; one already being spoken fails like any failed reply. At least `ttft_timeout_ms` when both are set. Not set: no limit.
        - `parallel` AgentLLMParallelPayload
          - `kick_in_ms` integer: The agent's own model keeps the reply if its first token comes within this many milliseconds; past it, this fallback's reply is used. The other request is cancelled.
      - `primary` AgentLLMTimeoutsPayload: The agent's own model's timeouts, write model. Unset ttft_timeout_ms = 5000 ms (5 seconds); unset total_timeout_ms = no limit. The two are compared only when both are set.
        - `ttft_timeout_ms` integer, nullable: Milliseconds the model gets to start its reply (first text or tool call). Past it, the reply moves to the next fallback model. Not set: defaults to 5000 ms (5 seconds).
        - `total_timeout_ms` integer, nullable: Milliseconds the model gets to finish its reply. Past it, a reply nothing of which was spoken yet moves to the next fallback model; one already being spoken fails like any failed reply. At least `ttft_timeout_ms` when both are set. Not set: no limit.
  - `variables` PublicAgentVariablesPatch
    - `defaults` object, nullable
    - `on_missing` 'keep' | 'empty' | 'reject', nullable

## Response `200`

Request fulfilled, document follows

- object
  - `agent_id` string, required
  - `config_hash` string, required: Opaque content hash of this configuration as stored. On a draft it is the config_hash the next publish gives the version, so it equals the live version's config_hash when nothing is unpublished. Pass it to Publish Agent as expected_config_hash.
  - `prompt` AgentPromptConfig, required
    - `system_prompt` string
    - `first_message_mode` 'off' | 'fixed' | 'prompt'
    - `first_message` string
    - `first_message_prompt` string
  - `voice` PublicAgentVoiceConfig, required: The voice section as the public API reads it: without the console-only sentence buffering switch.
    - `voice_id` string
    - `speaking_language` 'en' | 'ja' | 'zh' | 'ko' | 'es' | 'fr' | 'de' | 'pt' | 'it' | 'nl' | 'ar' | 'hi' | 'id' | 'tr' | 'ru' | 'bg' | 'sr' | 'hr' | 'cs' | 'sk' | 'pl' | 'uk' | 'ro' | 'hu' | 'el' | 'sv' | 'da' | 'no' | 'fi' | 'et' | 'lv' | 'lt' | 'ca' | 'he' | 'fa' | 'ur' | 'kk' | 'ka' | 'hy' | 'bn' | 'ta' | 'te' | 'kn' | 'mr' | 'gu' | 'pa' | 'ne' | 'th' | 'vi' | 'ms' | 'tl' | 'af'
    - `expressive` boolean
    - `speed` number
    - `tts_model` 's2.1-pro' | 'drama-3'
    - `pronunciation_dictionary_ids` string[]
  - `asr` PublicAgentAsrConfig, required: The asr section as the public API reads it: without the console-only noise cancellation and speaker label switches.
    - `model` 'deepgram:nova-3' | 'elevenlabs:scribe_v2_realtime' | 'elevenlabs:scribe_v2_medical' | 'fish_audio:transcribe-stream-1'
    - `multilingual` boolean
    - `strict_language` boolean: Enforces voice.speaking_language whatever multilingual says: speech recognized as another language reaches the agent as [unintelligible speech]. Language is detected per utterance, so a short or heavily accented phrase in the speaking language can occasionally be detected as another language and replaced too. With deepgram:nova-3, speech in another language is usually not transcribed at all. Only enable it when you explicitly need to stop the agent from understanding other languages.
    - `keyterms` string[]
  - `conversation` AgentConversationConfig, required
    - `max_duration_seconds` integer
    - `response_wait_ms` integer
    - `eagerness` 'relaxed' | 'balanced' | 'eager'
    - `response_max_wait_ms` integer, nullable
    - `interruptible` boolean
    - `interruption_sensitivity` 'low' | 'balanced' | 'high'
    - `interruption_ignore_phrases` string[]
    - `speculative_response` boolean
    - `reengage_enabled` boolean
    - `hangup_after_seconds` integer
    - `hangup_farewell_mode` 'generated' | 'fixed' | 'off': What the agent says before a silence hangup. `off` (default) hangs up without a word, `generated` says a short goodbye, `fixed` says hangup_farewell_message. A `fixed` mode with an empty message falls back to `generated`. If the caller speaks during the goodbye, the call goes on.
    - `hangup_farewell_message` string: The goodbye said verbatim in the `fixed` hangup_farewell_mode, at most 500 characters. `{{variable}}` placeholders are filled in for each session. "" clears it.
    - `record_audio` boolean
    - `timezone` string
    - `transfer_destinations` AgentTransferDestination[]
      - `type` 'phone' | 'sip'
      - `label` string
      - `phone_number` string, nullable
      - `sip_uri` string, nullable
      - `headers` object
      - `description` string
      - `mode` 'cold' | 'warm'
      - `warm_connect` 'confirm' | 'briefing' | 'direct'
      - `warm_briefing_instructions` string
      - `warm_ring_timeout_seconds` integer
      - `warm_confirm_timeout_seconds` integer
      - `warm_human_detection` boolean: Applies to warm transfers whose warm_connect is `confirm` or `briefing`, and is ignored for cold transfers and `direct`. When true, once the destination answers, the agent stays silent until the other side speaks, then classifies the pickup. A person is briefed as usual. Voicemail or an IVR (phone menu) ends the transfer as failed. A hold queue or silence keeps the agent waiting until `warm_confirm_timeout_seconds` ends the attempt as unreachable. A failed transfer follows `on_failure`. Leave it off for destinations whose people are only reachable through a phone menu, because the menu stops the transfer.
      - `warm_consult_uses_agent_settings` boolean: Compatibility option for warm transfers whose warm_connect is `confirm` or `briefing`, ignored for cold transfers and `direct`. Absent or true (recommended): on the call in which the agent briefs the destination, the agent uses the same turn-taking (response timing: `eagerness`, `response_wait_ms` and `response_max_wait_ms`), interruption (`interruption_sensitivity` and `interruption_ignore_phrases`) and language (`voice.speaking_language`) settings as on the call with the caller. False: that call keeps the older fixed behavior. Destinations that existed before this setting were set to false so their behavior did not change. Either way the destination can always interrupt the agent on that call, whatever `interruptible` is set to. A destination sent without the field keeps the value of the existing destination it replaces (same label, else a renamed one with the same phone number or SIP URI) and is true when it is new. Send true to switch a destination to the agent's settings.
      - `on_failure` AgentTransferOnFailure
        - `action` 'return_to_agent' | 'end_call'
        - `message` string
      - `announcement_mode` 'generated' | 'fixed' | 'off'
      - `announcement_message` string
    - `transfer_refuse_questions` boolean
    - `outbound` AgentOutboundConfig: Deprecated (2026-09-07): kept for compatibility only. `amd_enabled` and `voicemail` no longer change call behavior; outbound calls greet as soon as the callee answers, and `answered_by` is always `unknown`.
      - `amd_enabled` boolean
      - `voicemail` AgentOutboundVoicemailConfig: Deprecated (2026-09-07): answering-machine detection was removed from the call runtime, so voicemail actions never fire. The node is still accepted and stored so existing configs and API callers keep working; the worker no longer receives it.
        - `action` 'hangup' | 'leave_message' | 'leave_prompt_message'
        - `message` string
    - `keypad_menu` AgentKeypadMenu: Keypad menu for inbound phone calls. Before the agent's first turn it speaks `prompt` in the agent's voice, collects one key press (or, with `accept_speech`, the option's value said aloud) and stores the chosen option's `value` in the dynamic variable named by `variable`. The system prompt, first message, transfer texts, farewell and webhook tools can use `{{variable}}` to act on the choice. Outbound calls, web, API and test sessions skip the menu, and the variable renders from its default.
      - `enabled` boolean
      - `prompt` string
      - `options` AgentKeypadMenuOption[]
        - `key` string
        - `value` string
      - `accept_speech` boolean
      - `variable` string
      - `no_input_timeout_seconds` integer
      - `max_attempts` integer
      - `retry_message` string
      - `fallback_value` string
    - `keypad_input` AgentKeypadInput: Keypad input for phone calls: digits the caller types during the conversation reach the agent as one user turn, ended by `terminator` or by a pause of `inter_digit_timeout_seconds` (`none`: the pause only).
      - `enabled` boolean
      - `terminator` '#' | '*' | 'none'
      - `inter_digit_timeout_seconds` integer
    - `echo_guard` AgentEchoGuard: Echo guard per call channel: true on, false off, null uses the default.
      - `web_voice` boolean, nullable: Voice calls in a browser or app (web widget, SDK, test calls). Null uses the default (on).
      - `phone_inbound` boolean, nullable: Inbound phone calls. Null uses the default (on).
      - `phone_outbound` boolean, nullable: Outbound phone calls. Null uses the default (off).
  - `tools` PublicAgentToolsConfig, required
    - `enabled` boolean
    - `tool_ids` string[]
    - `system_tools` AgentSystemToolsConfig
      - `hang_up_call` boolean
      - `hang_up_call_farewell_mode` 'generated' | 'fixed' | 'off'
      - `hang_up_call_farewell_message` string
      - `hang_up_call_refuse_questions` boolean
  - `webhooks` PublicAgentWebhooksConfig, required
    - `conversation_init` PublicConversationInitWebhook
      - `url` string, required
      - `has_secret` boolean: Whether a signing secret is configured; the value itself is never returned.
      - `timeout_seconds` number
    - `post_call` PublicPostCallWebhook[]
      - `url` string, required
      - `has_secret` boolean: Whether a signing secret is configured; the value itself is never returned.
  - `knowledge_base` PublicAgentKnowledgeBaseConfig, required
    - `enabled` boolean
    - `knowledge_source_ids` string[]
  - `analysis` AgentAnalysisConfig, required
    - `summary` AgentAnalysisSummaryConfig
      - `enabled` boolean
      - `prompt` string, nullable
      - `language` 'en' | 'ja' | 'zh' | 'ko' | 'es' | 'fr' | 'de' | 'pt' | 'it' | 'nl' | 'ar' | 'hi' | 'id' | 'tr' | 'ru' | 'bg' | 'sr' | 'hr' | 'cs' | 'sk' | 'pl' | 'uk' | 'ro' | 'hu' | 'el' | 'sv' | 'da' | 'no' | 'fi' | 'et' | 'lv' | 'lt' | 'ca' | 'he' | 'fa' | 'ur' | 'kk' | 'ka' | 'hy' | 'bn' | 'ta' | 'te' | 'kn' | 'mr' | 'gu' | 'pa' | 'ne' | 'th' | 'vi' | 'ms' | 'tl' | 'af'
    - `data_fields` AgentAnalysisDataField[]
      - `name` string, required
      - `type` 'boolean' | 'text' | 'number' | 'enum'
      - `description` string
      - `enum_options` string[], nullable
    - `criteria` AgentAnalysisCriterion[]
      - `name` string, required
      - `description` string
  - `guardrails` AgentGuardrailsConfig, required
    - `focus` boolean
    - `manipulation` boolean
    - `pre_tool_speech` boolean
  - `context` AgentContextConfig, required: Facts the platform tells the agent on its own, one switch per fact. The same mechanism as a guardrail with the opposite sign: a guardrail is a prompt addition that constrains, a context switch is one that informs.
    - `current_time` boolean
  - `llm` AgentLLMConfigRedacted, required
    - `model` 'google/gemini-3.5-flash-lite' | 'google/gemini-3.6-flash' | 'google/gemini-3.8-flash' | 'google/gemini-3.6-flash-priority' | 'google/gemini-3.8-flash-priority' | 'google/gemini-2.5-flash' | 'google/gemma-4-26b-a4b-it' | 'anthropic/claude-haiku-4.5' | 'anthropic/claude-sonnet-4.6' | 'anthropic/claude-opus-5.5' | 'anthropic/claude-fable-5.1' | 'openai/gpt-5.6-luna' | 'openai/gpt-6-luna' | 'openai/gpt-5' | 'openai/gpt-6.1-sol' | 'openai/gpt-6-astra' | 'openai/gpt-4o' | 'openai/gpt-4.1'
    - `custom` AgentLLMCustomConfigRedacted: Read entity: the stored api_key never leaves the collection. The headers read back as stored, like the non-credential headers of a webhook tool.
      - `base_url` string, required
      - `model` string, required
      - `api_key` unknown
      - `headers` object, nullable
    - `fallback` AgentLLMFallbackRedacted
      - `mode` 'platform' | 'manual' | 'off'
      - `models` AgentLLMFallbackModelRedacted[], nullable
        - `model` 'google/gemini-3.5-flash-lite' | 'google/gemini-3.6-flash' | 'google/gemini-3.8-flash' | 'google/gemini-3.6-flash-priority' | 'google/gemini-3.8-flash-priority' | 'google/gemini-2.5-flash' | 'google/gemma-4-26b-a4b-it' | 'anthropic/claude-haiku-4.5' | 'anthropic/claude-sonnet-4.6' | 'anthropic/claude-opus-5.5' | 'anthropic/claude-fable-5.1' | 'openai/gpt-5.6-luna' | 'openai/gpt-6-luna' | 'openai/gpt-5' | 'openai/gpt-6.1-sol' | 'openai/gpt-6-astra' | 'openai/gpt-4o' | 'openai/gpt-4.1', nullable
        - `custom` AgentLLMCustomConfigRedacted: Read entity: the stored api_key never leaves the collection. The headers read back as stored, like the non-credential headers of a webhook tool.
          - `base_url` string, required
          - `model` string, required
          - `api_key` unknown
          - `headers` object, nullable
        - `ttft_timeout_ms` integer, nullable
        - `total_timeout_ms` integer, nullable
        - `parallel` AgentLLMParallel
          - `kick_in_ms` integer
      - `primary` AgentLLMTimeouts: How long one model gets per reply (manual only), read model.
        - `ttft_timeout_ms` integer, nullable
        - `total_timeout_ms` integer, nullable
  - `variables` AgentVariablesConfig: Defaults for `{{name}}` placeholders and what a placeholder without a value does.
    - `defaults` object
    - `on_missing` 'keep' | 'empty' | 'reject'
  - `updated_at` string, date-time, required

## Other responses

- `401`: No permission -- see authorization schemes
- `403`: Request forbidden -- authorization will not help
- `404`: Nothing matches the given URI
- `409`: Request conflict
- `422`
- `503`: The server cannot process the request due to a high load

## Changes

- **2026-11-03** `42ac421f7a12`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/fish/apis/fishaudio-openapi/changes/v1/agent/agents/:agent_id/config/patch.md)

---

[API](https://skmtc.dev/fish/apis/fishaudio-openapi.md) - [All operations](https://skmtc.dev/fish/apis/fishaudio-openapi/llms.txt) - [OpenAPI document](https://skmtc.dev/fish/apis/fishaudio-openapi/revisions/42ac421f7a12?raw)
