---
title: "Publish Agent"
method: POST
path: "/v1/agent/agents/{agent_id}/publish"
tags: ["Agents"]
---

# Publish Agent

`POST /v1/agent/agents/{agent_id}/publish`

## Request

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

Freeze the current draft into an immutable version and make it the live
configuration for new sessions. Each new version gets the next
version_number. If the draft already matches the live version, no version
is created and the live version comes back with `created: false`.

The response describes what was published:
- `version_number` and `config_hash` identify the version.
- `config` is the configuration exactly as published. Check your
  read-back against it, not against the request you sent.
- `created` tells whether this call made a new version.

Publishing copies the draft as it is stored. It does not change any field.
Writes do normalize some values, see Update Draft Config.

The body is optional:
- `expected_config_hash` publishes only while the draft still has that
  config_hash. Take it from Get Draft Config or Update Draft Config. On a
  mismatch the call returns 409 with `reason: "draft_changed"` and the
  draft's current `config_hash`. Nothing is written.
- `config` applies draft changes first, in the same shape as Update Draft
  Config. The changes and the publish are one atomic step. If either fails,
  the draft stays as it was.
- With both, `expected_config_hash` is checked against the draft before
  `config` is applied.
- `version_title` and `version_description` label a new version.

## Path parameters

- `agent_id` string, required

## Request body

- object
  - `version_title` string: Optional label shown in the version history.
  - `version_description` string: Optional longer note for audit and rollback context.
  - `expected_config_hash` string: Publish only while the draft has this config_hash. Otherwise 409 with reason `draft_changed` and nothing is written.
  - `config` 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
  - `version_number` integer, required
  - `version_title` string, nullable
  - `version_description` string, nullable
  - `config_hash` string, required
  - `published_at` string, date-time, required
  - `config` PublicAgentConfigEntity, required
    - `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
  - `created` boolean, required: Whether this call created a new version. False when the draft already matched the live version, which is returned unchanged.

## Other responses

- `401`: No permission -- see authorization schemes
- `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/publish/post.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)
