---
title: "Update Agent Version"
method: PUT
path: "/core/agents/{id}/versions/{version}"
tags: ["Agents"]
---

# Update Agent Version

`PUT /core/agents/{id}/versions/{version}`

## Request

- Base URL: `https://api.anyreach.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://api.anyreach.ai/core/agents/{id}/versions/{version}`
- Auth: HTTP bearer

Update a specific version of an agent.

## Path parameters

- `id` string, required
- `version` integer, required

## Headers

- `x-anyreach-org` string, nullable: Organization ID for user PATs (pat_ prefix tokens)

## Request body

- AgentVersionUpdateRequest: Schema for API requests to update an agent version.
  - `config` AgentConfig: Top-level agent configuration.
    - `version` '1'
    - `agent` StaticPromptAgent, required: Static prompt configuration.
      - `type` 'static_prompt'
      - `instructions` string
      - `starting_message` union, required
        - UserInitiatesMessage: User initiates starting message configuration.
          - `type` 'user_initiates'
          - `action_grid` StartingActionGrid: Optional action grid shown alongside the starting message (chat UI). Rendered on WebRTC sessions as a grid of quick-action buttons. Each item's ``label`` is sent back verbatim as the action value when the user taps it.
            - `title` string, nullable: Optional heading (defaults to 'How can I help?').
            - `items` StartingActionGridItem[]
              - …
        - BotInitiatesStaticMessage: Bot initiates static starting message configuration.
          - `type` 'bot_initiates_static'
          - `text` string, required
          - `literal` boolean: Say the text exactly as written, without rendering it as a template. Set on text that holds values from outside the agent config, such as a web widget's conversation starters.
          - `allow_interruptions` boolean
          - `action_grid` StartingActionGrid: Optional action grid shown alongside the starting message (chat UI). Rendered on WebRTC sessions as a grid of quick-action buttons. Each item's ``label`` is sent back verbatim as the action value when the user taps it.
            - `title` string, nullable: Optional heading (defaults to 'How can I help?').
            - `items` StartingActionGridItem[]
              - …
          - `pregenerated_greeting_audio` boolean: When true, the agent looks up a pre-generated recording of this greeting — synthesized once with the configured TTS voice and stored in the recording S3 bucket, content-addressed over ``text`` + the full TTS config — and plays it instead of synthesizing ``text`` with TTS on every conversation. Useful for truly-static greetings (e.g. inbound) where the same opening line is spoken every time. The recording is keyed by a fingerprint the runtime recomputes from the rendered text + TTS config, so if no recording matches (e.g. the text was edited but not regenerated, or the voice changed) the agent transparently falls back to live TTS. Only meaningful for non-templated ``text``; the builder pre-generates the recording.
        - BotInitiatesDynamicMessage: Bot initiates dynamic starting message configuration.
          - `type` 'bot_initiates_dynamic'
          - `prompt` string, required
          - `allow_interruptions` boolean
          - `action_grid` StartingActionGrid: Optional action grid shown alongside the starting message (chat UI). Rendered on WebRTC sessions as a grid of quick-action buttons. Each item's ``label`` is sent back verbatim as the action value when the user taps it.
            - `title` string, nullable: Optional heading (defaults to 'How can I help?').
            - `items` StartingActionGridItem[]
              - …
      - `tools` ToolCall[], required
        - `name` string, required
        - `description` string, required
        - `properties` object
        - `required` string[]
        - `actions` union[], required
          - union
            - PlayMessageAction
              - …
            - ColdTransferAction: Cold transfer configuration.
              - …
            - ConferenceAction: Conference configuration.
              - …
            - LeaveRoomAction
              - …
            - WaitAction
              - …
            - HangupAction
              - …
            - WorkflowAction
              - …
            - KnowledgeBaseAction
              - …
            - CustomDataAction: Read a Custom Data table during the call. Reads are served from Postgres in-VPC with no call to any external system on the live path. Filters and sorts address schema-declared properties only, and the SQL casts come from the declared types — so a price sorts as a number and a date as a date. Reads are the bulk of it. The one write is `claim`, which is deliberately the narrowest write that could exist: a fixed statement, a patch declared in config rather than supplied by the model, and no way to name a row directly. Everything wider stays out of the agent path (docs/prd/custom-data.md §5).
              - …
            - PlayAudioAction
              - …
            - StopAudioAction
              - …
            - WarmTransferAction: Warm transfer configuration.
              - …
            - DtmfAction: DTMF action configuration.
              - …
            - UpdateEndpointingAction: Update endpointing configuration.
              - …
            - HandoffAction: Hand this conversation to a human, as an action on any config tool. This is the ONLY way to define an escalation — there is no built-in handoff tool, exactly as there is no built-in cold transfer. The reason is routing: with Gladly the entry point + group IS the queue, so a support desk with billing / returns / technical needs one rule each. Domain-named tools (``escalate_billing_dispute``) also make the model markedly better at choosing than one generic tool plus a free-text reason. Everything here is per-ROUTE. Everything that belongs to the helpdesk rather than the route — the credentials, how long it takes to answer, what context it wants — lives on the ``handoff_connections`` row and is folded in by ``handoff_intent.resolve_handoff_action_config``. The split test is simply whether the value depends on the helpdesk or on this particular escalation. Customer-facing copy is deliberately here and not on the connection: it is spoken in the agent's voice and language, and one connection is shared by every agent in the org. ``custom_channel_id`` is rarely set. In Gladly a custom channel CONTAINS entry points, so queues under one channel share its id and only the entry point address varies; it exists for the one-channel-per-queue topology. The route fields above are the DEFAULT route. ``channels`` overrides them per channel, because a helpdesk generally exposes chat and email as separate integrations: same escalation, same description the model routes on, two destinations. Nothing else is per-channel — the copy is spoken in the agent's voice regardless of the medium. Like every other action this records INTENT and returns; the runtime opens the handoff at its own boundary. Nothing here raises across layers.
              - …
        - `actions_on_error` union[]
          - union
            - PlayMessageAction
              - …
            - ColdTransferAction: Cold transfer configuration.
              - …
            - ConferenceAction: Conference configuration.
              - …
            - LeaveRoomAction
              - …
            - WaitAction
              - …
            - HangupAction
              - …
            - WorkflowAction
              - …
            - KnowledgeBaseAction
              - …
            - CustomDataAction: Read a Custom Data table during the call. Reads are served from Postgres in-VPC with no call to any external system on the live path. Filters and sorts address schema-declared properties only, and the SQL casts come from the declared types — so a price sorts as a number and a date as a date. Reads are the bulk of it. The one write is `claim`, which is deliberately the narrowest write that could exist: a fixed statement, a patch declared in config rather than supplied by the model, and no way to name a row directly. Everything wider stays out of the agent path (docs/prd/custom-data.md §5).
              - …
            - PlayAudioAction
              - …
            - StopAudioAction
              - …
            - WarmTransferAction: Warm transfer configuration.
              - …
            - DtmfAction: DTMF action configuration.
              - …
            - UpdateEndpointingAction: Update endpointing configuration.
              - …
            - HandoffAction: Hand this conversation to a human, as an action on any config tool. This is the ONLY way to define an escalation — there is no built-in handoff tool, exactly as there is no built-in cold transfer. The reason is routing: with Gladly the entry point + group IS the queue, so a support desk with billing / returns / technical needs one rule each. Domain-named tools (``escalate_billing_dispute``) also make the model markedly better at choosing than one generic tool plus a free-text reason. Everything here is per-ROUTE. Everything that belongs to the helpdesk rather than the route — the credentials, how long it takes to answer, what context it wants — lives on the ``handoff_connections`` row and is folded in by ``handoff_intent.resolve_handoff_action_config``. The split test is simply whether the value depends on the helpdesk or on this particular escalation. Customer-facing copy is deliberately here and not on the connection: it is spoken in the agent's voice and language, and one connection is shared by every agent in the org. ``custom_channel_id`` is rarely set. In Gladly a custom channel CONTAINS entry points, so queues under one channel share its id and only the entry point address varies; it exists for the one-channel-per-queue topology. The route fields above are the DEFAULT route. ``channels`` overrides them per channel, because a helpdesk generally exposes chat and email as separate integrations: same escalation, same description the model routes on, two destinations. Nothing else is per-channel — the copy is spoken in the agent's voice regardless of the medium. Like every other action this records INTENT and returns; the runtime opens the handoff at its own boundary. Nothing here raises across layers.
              - …
        - `output` string, nullable
        - `skip_reply` string, nullable: Jinja expression evaluated after the tool finishes, whether its actions succeeded, failed or the identity gate refused it. True means the model writes no reply to this call. Reads arguments, data, steps, error (the failure's message or None) and refused (the refusal text or None). When the model calls several tools at once, the reply is skipped only if every one of them skips it
        - `requires_verified_identity` RequiresVerifiedIdentity: Gate a tool on a non-expired verified identity.
          - `subject_type` string, required
          - `methods` string[], nullable: Accepted verification methods; any method when omitted
          - `unverified_result` string, nullable: Jinja template the model gets instead of the default refusal
      - `built_in_tools` BuiltInTools: Configuration for platform built-in tools.
        - `time_tool` TimeToolConfig: Configuration for the get_current_time built-in tool.
          - `default_timezone` string, nullable: Default IANA timezone name (e.g., 'America/New_York'). Falls back to UTC if not provided.
        - `widgets` WidgetsConfig: Configuration for widget tools.
          - `allowed` WidgetType[], nullable: List of allowed widget types. If None/omitted, ALL widget types are enabled.
        - `forms` FormsConfig: Configuration for data-collection form tools shown during conversations.
          - `forms` FormDefinition[], required: Data-collection forms that can be shown as widgets during conversations.
            - `id` string, required: Unique identifier for this form within the agent, e.g. 'contact_info'.
            - `name` string, required: Display name in the builder UI.
            - `description` string, nullable: Tool description for the LLM to understand when to show this form.
            - `title` string, nullable: Title shown in the rendered form.
            - `submit_label` string, nullable
            - `fields` FormField[], required
              - …
        - `web_search` WebSearchToolConfig: Configuration for the web_search built-in tool.
        - `retrieve_webpage` RetrieveWebpageToolConfig: Configuration for the retrieve_webpage built-in tool.
        - `continue_on_phone` ContinueOnPhoneToolConfig: Configuration for the continue_on_phone built-in tool.
          - `agent_id` string, nullable: Agent to use for the phone call. Falls back to the current agent if not set.
          - `version` integer, nullable: Specific agent version number. Only used with agent_id. Uses latest published if not set.
          - `agent_number` string, nullable: E.164 phone number to call FROM. Falls back to the number assigned to the agent if not set.
        - `images` ImagesToolConfig: Configuration for the images built-in tool suite (analyze_image / list_images). `images: {}` (an empty object) enables the cheap default: floor schema only, fast tier, uploads allowed. `None` means the feature is off.
          - `routes` ImageTypeRoute[]
            - `image_type` string, required
            - `description` string, nullable: Shown to the classifier to sharpen routing.
            - `fields_schema` object: JSON-Schema `properties` for the typed fields of this image type.
            - `tier` string, nullable: Per-type tier override (e.g. start high for hard types).
          - `default_tier` string
          - `tiers` ImageTierConfig: Override of the platform tier ladder. Each slot holds one resolved provider.
            - `fast` union
              - …
            - `frontier` union
              - …
          - `max_escalations_per_conversation` integer
          - `max_analyses_per_conversation` integer
          - `allow_user_uploads` boolean
          - `floor_overrides` object, nullable
          - `pii_fields` string[]
          - `on_blocked` 'refuse' | 'flag'
        - `cart` CartToolConfig: Lets the agent add to, change and open the shopper's cart on the website, through the web widget. Platform- neutral: the widget's own ``v1.cart`` switch decides whether a site carries the actions out, and the embed loader's adapter for the store's platform (Shopify today) does them.
          - `max_quantity_per_line` integer: The most of one item the agent can add or set in one call.
          - `action_timeout_seconds` number: How long the agent waits for the shopper's browser to confirm.
      - `language` AgentLanguage: Agent language configuration.
        - `languages` string[], required
      - `stack` union, required
        - SttLlmTtsStack: STT-LLM-TTS stack configuration.
          - `type` 'stt_llm_tts'
          - `turn_detection` TurnDetection, required: Turn Detection configuration.
            - `models` union[], required
              - …
          - `stt` Stt, required: STT configuration.
            - `models` union[], required
              - …
            - `on_failure` SttOnFailure: Configuration for model failure handling.
              - …
          - `llm` Llm, required: LLM configuration.
            - `models` union[], required
              - …
            - `on_failure` LlmOnFailure: Configuration for model failure handling.
              - …
          - `tts` Tts, required: TTS configuration.
            - `models` union[], required
              - …
            - `on_failure` TtsOnFailure: Configuration for model failure handling.
              - …
            - `pronunciations` object, nullable
          - `vad` Vad: VAD configuration.
            - `models` SileroProvider[], required
              - …
          - `vision_llm` VisionLlm: Vision LLM for processing video frames.
            - `models` union[], required
              - …
            - `frame_rate` number, nullable
          - `interruption` InterruptionConfig: Interruption handling configuration.
            - `mode` 'adaptive' | 'vad'
            - `enabled` boolean
            - `min_duration` number, nullable
            - `min_words` integer, nullable
            - `false_interruption_timeout` number, nullable
            - `resume_false_interruption` boolean
          - `endpointing` EndpointingConfig: Endpointing configuration.
            - `min_delay` number
            - `max_delay` number
          - `text_normalization` TextNormalizationConfig: Text normalization applied to LLM output before TTS.
            - `enabled` boolean
            - `price_verbosity` 'formal' | 'conversational': Price pronunciation verbosity tier. Keep in sync with: backend/text-normalization-service/src/schemas/normalize.py:PriceVerbosity
            - `shorthand_cents_extra` integer[]: Extra cents values (0-99) that should use retail-style shorthand pronunciation under 'conversational' verbosity, in addition to the default set {49, 50, 95, 99}. Ignored under 'formal'.
            - `digit_group_separator` 'none' | 'comma' | 'ellipsis' | 'period': Punctuation inserted between groups of a spelled-out digit run. Keep in sync with: backend/text-normalization-service/src/services/normalizer.py :DIGIT_GROUP_SEPARATORS (source of truth) backend/text-normalization-service/src/schemas/normalize.py :DigitGroupSeparator
        - RealtimeStack: Realtime stack configuration.
          - `type` 'realtime'
          - `llm` Llm, required: LLM configuration.
            - `models` union[], required
              - …
            - `on_failure` LlmOnFailure: Configuration for model failure handling.
              - …
          - `turn_detection` TurnDetection: Turn Detection configuration.
            - `models` union[], required
              - …
          - `stt` Stt: STT configuration.
            - `models` union[], required
              - …
            - `on_failure` SttOnFailure: Configuration for model failure handling.
              - …
          - `vad` Vad: VAD configuration.
            - `models` SileroProvider[], required
              - …
          - `vision_llm` VisionLlm: Vision LLM for processing video frames.
            - `models` union[], required
              - …
            - `frame_rate` number, nullable
      - `allow_interruptions` boolean, nullable
      - `video_input` boolean, nullable
      - `screen_share_input` boolean, nullable
      - `max_tool_steps` integer, nullable: Maximum number of tool invocations per conversation turn. Defaults to 3 if not specified.
      - `channel_overrides` object, nullable: Group 1 — per-channel LLM steering (append/replace instructions, greeting, tool budget).
      - `channel_properties` object, nullable: Group 2 — per-channel transport/presentation (body template, segments, subject, signature, enabled).
    - `metadata` object, nullable
    - `noise_cancellation` NoiseCancellation: Noise cancellation configuration.
      - `models` LivekitProvider[], required
        - `provider` 'livekit'
        - `model` union, required
          - LivekitBVCModel: LiveKit BVC model configuration.
            - `name` 'bvc'
          - LivekitBVCTelephonyModel: LiveKit BVC Telephony model configuration.
            - `name` 'bvc_telephony'
    - `custom_recording_locations` union[], nullable
      - union
        - S3RecordingLocation: S3 recording location provider.
          - `provider` 's3'
          - `bucket` string, required
          - `region` string, required
          - `access_key` string, required
          - `secret_access_key` string, required
          - `file_type` 'ogg' | 'mp4'
        - GcpRecordingLocation: GCP recording location provider.
          - `provider` 'gcp'
          - `bucket` string, required
          - `credentials` string, required
          - `file_type` 'ogg' | 'mp4'
        - AzureRecordingLocation: Azure recording location provider.
          - `provider` 'azure'
          - `container_name` string, required
          - `account_name` string, required
          - `account_key` string, required
          - `file_type` 'ogg' | 'mp4'
    - `pre_conversation_workflow` WorkflowAction
      - `id` string, nullable: Name of this action's result in steps (steps.<id>). Defaults to its position counted from 1: steps.1, steps.2, … and steps.on_error_1, … in Actions on Error
      - `type` 'workflow'
      - `workflow_id` string, nullable
      - `workflow_version` integer, nullable
      - `initial_step` string, nullable
      - `definition` object, nullable: Inline workflow definition (mutually exclusive with workflow_id/workflow_version)
      - `condition` string, nullable
      - `reject_on_error` boolean: If true, abort the conversation when the workflow fails
      - `verifies_identity` VerifiesIdentity: Marks a workflow as an identity verifier: the only writer of ``verified_identities``; its ``custom_metadata`` output is never saved.
        - `subject_type` string, required
        - `output_schema` object, nullable: JSON Schema checked against {"identifiers": ..., "attributes": ...}
        - `ttl` integer: How long a verification stays valid; at most 7 days
        - `lockout` VerifierLockout: Per-conversation limit on a verifier tool's rejected attempts.
          - `max_failures` integer: Rejected tool-trigger attempts in one conversation before the verifier locks
      - `input_exclude` string[], nullable: Conversation parts left out of the workflow's input: agent_instructions, transcript, tool_outputs, transcript_internals. Unset sends everything
      - `include_page_view` boolean: Web chat with page context: add what was on the visitor's screen when they sent their latest message (conversation_metadata.page_view), if it was read. Off sends only where each message was sent from.
    - `identity_verifiers` IdentityVerifier[]
      - `id` string, nullable: Name of this action's result in steps (steps.<id>). Defaults to its position counted from 1: steps.1, steps.2, … and steps.on_error_1, … in Actions on Error
      - `type` 'workflow'
      - `workflow_id` string, nullable
      - `workflow_version` integer, nullable
      - `initial_step` string, nullable
      - `definition` object, nullable: Inline workflow definition (mutually exclusive with workflow_id/workflow_version)
      - `condition` string, nullable
      - `reject_on_error` boolean: If true, abort the conversation when the workflow fails
      - `verifies_identity` VerifiesIdentity: Marks a workflow as an identity verifier: the only writer of ``verified_identities``; its ``custom_metadata`` output is never saved.
        - `subject_type` string, required
        - `output_schema` object, nullable: JSON Schema checked against {"identifiers": ..., "attributes": ...}
        - `ttl` integer: How long a verification stays valid; at most 7 days
        - `lockout` VerifierLockout: Per-conversation limit on a verifier tool's rejected attempts.
          - `max_failures` integer: Rejected tool-trigger attempts in one conversation before the verifier locks
      - `input_exclude` string[], nullable: Conversation parts left out of the workflow's input: agent_instructions, transcript, tool_outputs, transcript_internals. Unset sends everything
      - `include_page_view` boolean: Web chat with page context: add what was on the visitor's screen when they sent their latest message (conversation_metadata.page_view), if it was read. Off sends only where each message was sent from.
      - `timing` 'background' | 'before_start': Conversation start only: before the greeting, or alongside it
    - `post_conversation_workflow` WorkflowAction
      - `id` string, nullable: Name of this action's result in steps (steps.<id>). Defaults to its position counted from 1: steps.1, steps.2, … and steps.on_error_1, … in Actions on Error
      - `type` 'workflow'
      - `workflow_id` string, nullable
      - `workflow_version` integer, nullable
      - `initial_step` string, nullable
      - `definition` object, nullable: Inline workflow definition (mutually exclusive with workflow_id/workflow_version)
      - `condition` string, nullable
      - `reject_on_error` boolean: If true, abort the conversation when the workflow fails
      - `verifies_identity` VerifiesIdentity: Marks a workflow as an identity verifier: the only writer of ``verified_identities``; its ``custom_metadata`` output is never saved.
        - `subject_type` string, required
        - `output_schema` object, nullable: JSON Schema checked against {"identifiers": ..., "attributes": ...}
        - `ttl` integer: How long a verification stays valid; at most 7 days
        - `lockout` VerifierLockout: Per-conversation limit on a verifier tool's rejected attempts.
          - `max_failures` integer: Rejected tool-trigger attempts in one conversation before the verifier locks
      - `input_exclude` string[], nullable: Conversation parts left out of the workflow's input: agent_instructions, transcript, tool_outputs, transcript_internals. Unset sends everything
      - `include_page_view` boolean: Web chat with page context: add what was on the visitor's screen when they sent their latest message (conversation_metadata.page_view), if it was read. Off sends only where each message was sent from.
    - `extraction_workflow` WorkflowAction
      - `id` string, nullable: Name of this action's result in steps (steps.<id>). Defaults to its position counted from 1: steps.1, steps.2, … and steps.on_error_1, … in Actions on Error
      - `type` 'workflow'
      - `workflow_id` string, nullable
      - `workflow_version` integer, nullable
      - `initial_step` string, nullable
      - `definition` object, nullable: Inline workflow definition (mutually exclusive with workflow_id/workflow_version)
      - `condition` string, nullable
      - `reject_on_error` boolean: If true, abort the conversation when the workflow fails
      - `verifies_identity` VerifiesIdentity: Marks a workflow as an identity verifier: the only writer of ``verified_identities``; its ``custom_metadata`` output is never saved.
        - `subject_type` string, required
        - `output_schema` object, nullable: JSON Schema checked against {"identifiers": ..., "attributes": ...}
        - `ttl` integer: How long a verification stays valid; at most 7 days
        - `lockout` VerifierLockout: Per-conversation limit on a verifier tool's rejected attempts.
          - `max_failures` integer: Rejected tool-trigger attempts in one conversation before the verifier locks
      - `input_exclude` string[], nullable: Conversation parts left out of the workflow's input: agent_instructions, transcript, tool_outputs, transcript_internals. Unset sends everything
      - `include_page_view` boolean: Web chat with page context: add what was on the visitor's screen when they sent their latest message (conversation_metadata.page_view), if it was read. Off sends only where each message was sent from.
    - `on_idle` OnIdleConfig: Where a quiet conversation is filed in the helpdesk (the idle archive). The same actions, conditions and ``steps`` as a tool, with no ``arguments``: no model called anything. Workflows run in order, and the first ``handoff`` whose condition holds and which has a connection on this channel is the route; nothing after it runs. None holding means the conversation is not filed. Of a ``handoff`` only the route and the condition apply: an archive says nothing to the customer. See ``services/handoff_archive.py``.
      - `actions` union[], required
        - union
          - WorkflowAction
            - `id` string, nullable: Name of this action's result in steps (steps.<id>). Defaults to its position counted from 1: steps.1, steps.2, … and steps.on_error_1, … in Actions on Error
            - `type` 'workflow'
            - `workflow_id` string, nullable
            - `workflow_version` integer, nullable
            - `initial_step` string, nullable
            - `definition` object, nullable: Inline workflow definition (mutually exclusive with workflow_id/workflow_version)
            - `condition` string, nullable
            - `reject_on_error` boolean: If true, abort the conversation when the workflow fails
            - `verifies_identity` VerifiesIdentity: Marks a workflow as an identity verifier: the only writer of ``verified_identities``; its ``custom_metadata`` output is never saved.
              - …
            - `input_exclude` string[], nullable: Conversation parts left out of the workflow's input: agent_instructions, transcript, tool_outputs, transcript_internals. Unset sends everything
            - `include_page_view` boolean: Web chat with page context: add what was on the visitor's screen when they sent their latest message (conversation_metadata.page_view), if it was read. Off sends only where each message was sent from.
          - HandoffAction: Hand this conversation to a human, as an action on any config tool. This is the ONLY way to define an escalation — there is no built-in handoff tool, exactly as there is no built-in cold transfer. The reason is routing: with Gladly the entry point + group IS the queue, so a support desk with billing / returns / technical needs one rule each. Domain-named tools (``escalate_billing_dispute``) also make the model markedly better at choosing than one generic tool plus a free-text reason. Everything here is per-ROUTE. Everything that belongs to the helpdesk rather than the route — the credentials, how long it takes to answer, what context it wants — lives on the ``handoff_connections`` row and is folded in by ``handoff_intent.resolve_handoff_action_config``. The split test is simply whether the value depends on the helpdesk or on this particular escalation. Customer-facing copy is deliberately here and not on the connection: it is spoken in the agent's voice and language, and one connection is shared by every agent in the org. ``custom_channel_id`` is rarely set. In Gladly a custom channel CONTAINS entry points, so queues under one channel share its id and only the entry point address varies; it exists for the one-channel-per-queue topology. The route fields above are the DEFAULT route. ``channels`` overrides them per channel, because a helpdesk generally exposes chat and email as separate integrations: same escalation, same description the model routes on, two destinations. Nothing else is per-channel — the copy is spoken in the agent's voice regardless of the medium. Like every other action this records INTENT and returns; the runtime opens the handoff at its own boundary. Nothing here raises across layers.
            - `id` string, nullable: Name of this action's result in steps (steps.<id>). Defaults to its position counted from 1: steps.1, steps.2, … and steps.on_error_1, … in Actions on Error
            - `type` 'handoff'
            - `connection_id` string, nullable: handoff_connections row to escalate through — the helpdesk, its credentials and its timers.
            - `entry_point_address` string, nullable: Provider-side inbox this escalation lands in (Gladly: the Entry Point 'Address'). This is the queue.
            - `custom_channel_id` string, nullable: Usually blank. Entry points under one custom channel share its id; set this only if this queue is on a different channel.
            - `group_id` string, nullable: Optional message-group id, sent alongside the entry point.
            - `group_name` string, nullable: Optional message-group name, sent alongside group_id.
            - `channels` object, nullable: Override where this rule lands, per channel. One helpdesk usually needs a different connection for chat than for email, and this is the same rule either way — same name, same description, same copy. Only the channels with a relay can escalate at all (see handoff_intent.RELAY_CHANNELS); an entry for any other is inert.
            - `handoff_message` string, nullable: Deterministic reply sent the moment this handoff opens (the model never paraphrases it).
            - `send_handoff_message` boolean, nullable: EMAIL ONLY. Turn off to open the escalation silently: the helpdesk gets the conversation and the customer's next mail is the human's reply, with no 'connecting you' message in between. Some teams read that message as a second automated reply to an inbox that already auto-acknowledged. On chat and voice the copy is always sent — silence there reads as the agent having stopped answering.
            - `waiting_message` string, nullable: Posted when no human has replied within the connection's no-reply window.
            - `unavailable_message` string, nullable: Said to the customer when this handoff could not be opened. The bot must never promise a human it cannot reach.
            - `on_unavailable` 'stay_with_bot' | 'end', nullable: What to do when the handoff cannot be opened.
            - `require_identity` 'off' | 'email' | 'phone' | 'either', nullable: Contact details this escalation must have before it opens. When the conversation does not already contain them, the customer is shown a form and the handoff opens on submit. 'off' (the default) escalates immediately.
            - `identity_message` string, nullable: Said to the customer when the form is shown, in place of the handoff message.
            - `identity_form_title` string, nullable: Heading on the form shown to the customer.
            - `include_attachments` boolean, nullable: Forward the photos and files the customer shared with the agent to the human, and note them in the pushed transcript. Off by default: an escalation about a late order should not carry the ID photo someone uploaded earlier to verify a discount. A message that was nothing but attachments is still noted as such when off, or it would vanish from the transcript entirely.
            - `condition` string, nullable
    - `scorecard_ids` string[], nullable
    - `voicemail_detection` VoicemailDetectionConfig: Configuration for voicemail detection and response behavior.
      - `beep_detection_threshold` number, required: Confidence threshold (0.0-1.0) for background beep detection. Having above 0.95 yields better result
      - `beep_detection_timeout` integer, required: Max seconds to run background beep detection
      - `tool_call_overrides` VoicemailToolCallOverrides: Overrides for voicemail tool-call based detection and prompt handling. If not provided, then the default values will be used. beep_detection_threshold: 0.9 beep_detection_timeout: 10 name: voicemail_detected description: whenever you hear 'you have reached a voicemail'
        - `beep_detection_threshold` number: Confidence threshold (0.0-1.0) for beep after voice prompt
        - `beep_detection_timeout` integer: Max seconds to wait for beep after voicemail voice prompt
        - `name` string: Tool call function name the LLM can invoke (e.g., 'voicemail_detected')
        - `description` string: Description/when to invoke the tool (e.g., when voicemail greeting is detected)
      - `play_message` VoicemailPlayMessageAction
        - `message` string, required
        - `generate` boolean
    - `inactivity_nudge` InactivityNudgeConfig: Nudge the user on silence, then hang up after the nudges go unanswered. Voice only (telephony + WebRTC voice). Driven by LiveKit's VAD-based ``away`` user state, so it has no effect on text/chat conversations. Always on — there is no enable/disable toggle; tune the timings and nudge copy instead.
      - `timeout_seconds` number: Silence before the first nudge (feeds LiveKit user_away_timeout).
      - `nudge_interval_seconds` number: Delay between nudges, and the final grace period before hangup.
      - `nudges` union[]: Ordered, one per attempt (escalating copy). Hang up after the last.
        - union
          - NudgeStaticMessage: Inactivity nudge spoken as exact text (verbatim via TTS).
            - `type` 'bot_initiates_static'
            - `text` string, required
          - NudgeDynamicMessage: Inactivity nudge generated by the LLM from instructions.
            - `type` 'bot_initiates_dynamic'
            - `prompt` string, required
    - `screen_pop` object, nullable: Named links that /<org>/screen-pop/<caller>?target=<name> opens for a human taking over a call from this agent.
  - `instruction_builder_inputs` InstructionBuilderInputs: Structured inputs for the instruction builder.
    - `conversation_flow` string, nullable
    - `guardrails` string, nullable
    - `disallowed_topics` string, nullable
    - `safety_rules` string[], nullable
    - `enrichment_data` object, nullable
  - `expected_updated_at` string, date-time, nullable: The version's `updated_at` as the caller last loaded it. When set, the save is refused with 409 if the version changed since, instead of overwriting that change. Send back exactly the value the API returned: it carries microseconds.

## Response `200`

Successful Response

- AgentVersion: Agent version entity representing a version of an agent with configuration.
  - `id` string, required
  - `organization_id` string, required
  - `agent_id` string, required
  - `version` integer, required
  - `config` AgentConfig, required: Top-level agent configuration.
    - `version` '1'
    - `agent` StaticPromptAgent, required: Static prompt configuration.
      - `type` 'static_prompt'
      - `instructions` string
      - `starting_message` union, required
        - UserInitiatesMessage: User initiates starting message configuration.
          - `type` 'user_initiates'
          - `action_grid` StartingActionGrid: Optional action grid shown alongside the starting message (chat UI). Rendered on WebRTC sessions as a grid of quick-action buttons. Each item's ``label`` is sent back verbatim as the action value when the user taps it.
            - `title` string, nullable: Optional heading (defaults to 'How can I help?').
            - `items` StartingActionGridItem[]
              - …
        - BotInitiatesStaticMessage: Bot initiates static starting message configuration.
          - `type` 'bot_initiates_static'
          - `text` string, required
          - `literal` boolean: Say the text exactly as written, without rendering it as a template. Set on text that holds values from outside the agent config, such as a web widget's conversation starters.
          - `allow_interruptions` boolean
          - `action_grid` StartingActionGrid: Optional action grid shown alongside the starting message (chat UI). Rendered on WebRTC sessions as a grid of quick-action buttons. Each item's ``label`` is sent back verbatim as the action value when the user taps it.
            - `title` string, nullable: Optional heading (defaults to 'How can I help?').
            - `items` StartingActionGridItem[]
              - …
          - `pregenerated_greeting_audio` boolean: When true, the agent looks up a pre-generated recording of this greeting — synthesized once with the configured TTS voice and stored in the recording S3 bucket, content-addressed over ``text`` + the full TTS config — and plays it instead of synthesizing ``text`` with TTS on every conversation. Useful for truly-static greetings (e.g. inbound) where the same opening line is spoken every time. The recording is keyed by a fingerprint the runtime recomputes from the rendered text + TTS config, so if no recording matches (e.g. the text was edited but not regenerated, or the voice changed) the agent transparently falls back to live TTS. Only meaningful for non-templated ``text``; the builder pre-generates the recording.
        - BotInitiatesDynamicMessage: Bot initiates dynamic starting message configuration.
          - `type` 'bot_initiates_dynamic'
          - `prompt` string, required
          - `allow_interruptions` boolean
          - `action_grid` StartingActionGrid: Optional action grid shown alongside the starting message (chat UI). Rendered on WebRTC sessions as a grid of quick-action buttons. Each item's ``label`` is sent back verbatim as the action value when the user taps it.
            - `title` string, nullable: Optional heading (defaults to 'How can I help?').
            - `items` StartingActionGridItem[]
              - …
      - `tools` ToolCall[], required
        - `name` string, required
        - `description` string, required
        - `properties` object
        - `required` string[]
        - `actions` union[], required
          - union
            - PlayMessageAction
              - …
            - ColdTransferAction: Cold transfer configuration.
              - …
            - ConferenceAction: Conference configuration.
              - …
            - LeaveRoomAction
              - …
            - WaitAction
              - …
            - HangupAction
              - …
            - WorkflowAction
              - …
            - KnowledgeBaseAction
              - …
            - CustomDataAction: Read a Custom Data table during the call. Reads are served from Postgres in-VPC with no call to any external system on the live path. Filters and sorts address schema-declared properties only, and the SQL casts come from the declared types — so a price sorts as a number and a date as a date. Reads are the bulk of it. The one write is `claim`, which is deliberately the narrowest write that could exist: a fixed statement, a patch declared in config rather than supplied by the model, and no way to name a row directly. Everything wider stays out of the agent path (docs/prd/custom-data.md §5).
              - …
            - PlayAudioAction
              - …
            - StopAudioAction
              - …
            - WarmTransferAction: Warm transfer configuration.
              - …
            - DtmfAction: DTMF action configuration.
              - …
            - UpdateEndpointingAction: Update endpointing configuration.
              - …
            - HandoffAction: Hand this conversation to a human, as an action on any config tool. This is the ONLY way to define an escalation — there is no built-in handoff tool, exactly as there is no built-in cold transfer. The reason is routing: with Gladly the entry point + group IS the queue, so a support desk with billing / returns / technical needs one rule each. Domain-named tools (``escalate_billing_dispute``) also make the model markedly better at choosing than one generic tool plus a free-text reason. Everything here is per-ROUTE. Everything that belongs to the helpdesk rather than the route — the credentials, how long it takes to answer, what context it wants — lives on the ``handoff_connections`` row and is folded in by ``handoff_intent.resolve_handoff_action_config``. The split test is simply whether the value depends on the helpdesk or on this particular escalation. Customer-facing copy is deliberately here and not on the connection: it is spoken in the agent's voice and language, and one connection is shared by every agent in the org. ``custom_channel_id`` is rarely set. In Gladly a custom channel CONTAINS entry points, so queues under one channel share its id and only the entry point address varies; it exists for the one-channel-per-queue topology. The route fields above are the DEFAULT route. ``channels`` overrides them per channel, because a helpdesk generally exposes chat and email as separate integrations: same escalation, same description the model routes on, two destinations. Nothing else is per-channel — the copy is spoken in the agent's voice regardless of the medium. Like every other action this records INTENT and returns; the runtime opens the handoff at its own boundary. Nothing here raises across layers.
              - …
        - `actions_on_error` union[]
          - union
            - PlayMessageAction
              - …
            - ColdTransferAction: Cold transfer configuration.
              - …
            - ConferenceAction: Conference configuration.
              - …
            - LeaveRoomAction
              - …
            - WaitAction
              - …
            - HangupAction
              - …
            - WorkflowAction
              - …
            - KnowledgeBaseAction
              - …
            - CustomDataAction: Read a Custom Data table during the call. Reads are served from Postgres in-VPC with no call to any external system on the live path. Filters and sorts address schema-declared properties only, and the SQL casts come from the declared types — so a price sorts as a number and a date as a date. Reads are the bulk of it. The one write is `claim`, which is deliberately the narrowest write that could exist: a fixed statement, a patch declared in config rather than supplied by the model, and no way to name a row directly. Everything wider stays out of the agent path (docs/prd/custom-data.md §5).
              - …
            - PlayAudioAction
              - …
            - StopAudioAction
              - …
            - WarmTransferAction: Warm transfer configuration.
              - …
            - DtmfAction: DTMF action configuration.
              - …
            - UpdateEndpointingAction: Update endpointing configuration.
              - …
            - HandoffAction: Hand this conversation to a human, as an action on any config tool. This is the ONLY way to define an escalation — there is no built-in handoff tool, exactly as there is no built-in cold transfer. The reason is routing: with Gladly the entry point + group IS the queue, so a support desk with billing / returns / technical needs one rule each. Domain-named tools (``escalate_billing_dispute``) also make the model markedly better at choosing than one generic tool plus a free-text reason. Everything here is per-ROUTE. Everything that belongs to the helpdesk rather than the route — the credentials, how long it takes to answer, what context it wants — lives on the ``handoff_connections`` row and is folded in by ``handoff_intent.resolve_handoff_action_config``. The split test is simply whether the value depends on the helpdesk or on this particular escalation. Customer-facing copy is deliberately here and not on the connection: it is spoken in the agent's voice and language, and one connection is shared by every agent in the org. ``custom_channel_id`` is rarely set. In Gladly a custom channel CONTAINS entry points, so queues under one channel share its id and only the entry point address varies; it exists for the one-channel-per-queue topology. The route fields above are the DEFAULT route. ``channels`` overrides them per channel, because a helpdesk generally exposes chat and email as separate integrations: same escalation, same description the model routes on, two destinations. Nothing else is per-channel — the copy is spoken in the agent's voice regardless of the medium. Like every other action this records INTENT and returns; the runtime opens the handoff at its own boundary. Nothing here raises across layers.
              - …
        - `output` string, nullable
        - `skip_reply` string, nullable: Jinja expression evaluated after the tool finishes, whether its actions succeeded, failed or the identity gate refused it. True means the model writes no reply to this call. Reads arguments, data, steps, error (the failure's message or None) and refused (the refusal text or None). When the model calls several tools at once, the reply is skipped only if every one of them skips it
        - `requires_verified_identity` RequiresVerifiedIdentity: Gate a tool on a non-expired verified identity.
          - `subject_type` string, required
          - `methods` string[], nullable: Accepted verification methods; any method when omitted
          - `unverified_result` string, nullable: Jinja template the model gets instead of the default refusal
      - `built_in_tools` BuiltInTools: Configuration for platform built-in tools.
        - `time_tool` TimeToolConfig: Configuration for the get_current_time built-in tool.
          - `default_timezone` string, nullable: Default IANA timezone name (e.g., 'America/New_York'). Falls back to UTC if not provided.
        - `widgets` WidgetsConfig: Configuration for widget tools.
          - `allowed` WidgetType[], nullable: List of allowed widget types. If None/omitted, ALL widget types are enabled.
        - `forms` FormsConfig: Configuration for data-collection form tools shown during conversations.
          - `forms` FormDefinition[], required: Data-collection forms that can be shown as widgets during conversations.
            - `id` string, required: Unique identifier for this form within the agent, e.g. 'contact_info'.
            - `name` string, required: Display name in the builder UI.
            - `description` string, nullable: Tool description for the LLM to understand when to show this form.
            - `title` string, nullable: Title shown in the rendered form.
            - `submit_label` string, nullable
            - `fields` FormField[], required
              - …
        - `web_search` WebSearchToolConfig: Configuration for the web_search built-in tool.
        - `retrieve_webpage` RetrieveWebpageToolConfig: Configuration for the retrieve_webpage built-in tool.
        - `continue_on_phone` ContinueOnPhoneToolConfig: Configuration for the continue_on_phone built-in tool.
          - `agent_id` string, nullable: Agent to use for the phone call. Falls back to the current agent if not set.
          - `version` integer, nullable: Specific agent version number. Only used with agent_id. Uses latest published if not set.
          - `agent_number` string, nullable: E.164 phone number to call FROM. Falls back to the number assigned to the agent if not set.
        - `images` ImagesToolConfig: Configuration for the images built-in tool suite (analyze_image / list_images). `images: {}` (an empty object) enables the cheap default: floor schema only, fast tier, uploads allowed. `None` means the feature is off.
          - `routes` ImageTypeRoute[]
            - `image_type` string, required
            - `description` string, nullable: Shown to the classifier to sharpen routing.
            - `fields_schema` object: JSON-Schema `properties` for the typed fields of this image type.
            - `tier` string, nullable: Per-type tier override (e.g. start high for hard types).
          - `default_tier` string
          - `tiers` ImageTierConfig: Override of the platform tier ladder. Each slot holds one resolved provider.
            - `fast` union
              - …
            - `frontier` union
              - …
          - `max_escalations_per_conversation` integer
          - `max_analyses_per_conversation` integer
          - `allow_user_uploads` boolean
          - `floor_overrides` object, nullable
          - `pii_fields` string[]
          - `on_blocked` 'refuse' | 'flag'
        - `cart` CartToolConfig: Lets the agent add to, change and open the shopper's cart on the website, through the web widget. Platform- neutral: the widget's own ``v1.cart`` switch decides whether a site carries the actions out, and the embed loader's adapter for the store's platform (Shopify today) does them.
          - `max_quantity_per_line` integer: The most of one item the agent can add or set in one call.
          - `action_timeout_seconds` number: How long the agent waits for the shopper's browser to confirm.
      - `language` AgentLanguage: Agent language configuration.
        - `languages` string[], required
      - `stack` union, required
        - SttLlmTtsStack: STT-LLM-TTS stack configuration.
          - `type` 'stt_llm_tts'
          - `turn_detection` TurnDetection, required: Turn Detection configuration.
            - `models` union[], required
              - …
          - `stt` Stt, required: STT configuration.
            - `models` union[], required
              - …
            - `on_failure` SttOnFailure: Configuration for model failure handling.
              - …
          - `llm` Llm, required: LLM configuration.
            - `models` union[], required
              - …
            - `on_failure` LlmOnFailure: Configuration for model failure handling.
              - …
          - `tts` Tts, required: TTS configuration.
            - `models` union[], required
              - …
            - `on_failure` TtsOnFailure: Configuration for model failure handling.
              - …
            - `pronunciations` object, nullable
          - `vad` Vad: VAD configuration.
            - `models` SileroProvider[], required
              - …
          - `vision_llm` VisionLlm: Vision LLM for processing video frames.
            - `models` union[], required
              - …
            - `frame_rate` number, nullable
          - `interruption` InterruptionConfig: Interruption handling configuration.
            - `mode` 'adaptive' | 'vad'
            - `enabled` boolean
            - `min_duration` number, nullable
            - `min_words` integer, nullable
            - `false_interruption_timeout` number, nullable
            - `resume_false_interruption` boolean
          - `endpointing` EndpointingConfig: Endpointing configuration.
            - `min_delay` number
            - `max_delay` number
          - `text_normalization` TextNormalizationConfig: Text normalization applied to LLM output before TTS.
            - `enabled` boolean
            - `price_verbosity` 'formal' | 'conversational': Price pronunciation verbosity tier. Keep in sync with: backend/text-normalization-service/src/schemas/normalize.py:PriceVerbosity
            - `shorthand_cents_extra` integer[]: Extra cents values (0-99) that should use retail-style shorthand pronunciation under 'conversational' verbosity, in addition to the default set {49, 50, 95, 99}. Ignored under 'formal'.
            - `digit_group_separator` 'none' | 'comma' | 'ellipsis' | 'period': Punctuation inserted between groups of a spelled-out digit run. Keep in sync with: backend/text-normalization-service/src/services/normalizer.py :DIGIT_GROUP_SEPARATORS (source of truth) backend/text-normalization-service/src/schemas/normalize.py :DigitGroupSeparator
        - RealtimeStack: Realtime stack configuration.
          - `type` 'realtime'
          - `llm` Llm, required: LLM configuration.
            - `models` union[], required
              - …
            - `on_failure` LlmOnFailure: Configuration for model failure handling.
              - …
          - `turn_detection` TurnDetection: Turn Detection configuration.
            - `models` union[], required
              - …
          - `stt` Stt: STT configuration.
            - `models` union[], required
              - …
            - `on_failure` SttOnFailure: Configuration for model failure handling.
              - …
          - `vad` Vad: VAD configuration.
            - `models` SileroProvider[], required
              - …
          - `vision_llm` VisionLlm: Vision LLM for processing video frames.
            - `models` union[], required
              - …
            - `frame_rate` number, nullable
      - `allow_interruptions` boolean, nullable
      - `video_input` boolean, nullable
      - `screen_share_input` boolean, nullable
      - `max_tool_steps` integer, nullable: Maximum number of tool invocations per conversation turn. Defaults to 3 if not specified.
      - `channel_overrides` object, nullable: Group 1 — per-channel LLM steering (append/replace instructions, greeting, tool budget).
      - `channel_properties` object, nullable: Group 2 — per-channel transport/presentation (body template, segments, subject, signature, enabled).
    - `metadata` object, nullable
    - `noise_cancellation` NoiseCancellation: Noise cancellation configuration.
      - `models` LivekitProvider[], required
        - `provider` 'livekit'
        - `model` union, required
          - LivekitBVCModel: LiveKit BVC model configuration.
            - `name` 'bvc'
          - LivekitBVCTelephonyModel: LiveKit BVC Telephony model configuration.
            - `name` 'bvc_telephony'
    - `custom_recording_locations` union[], nullable
      - union
        - S3RecordingLocation: S3 recording location provider.
          - `provider` 's3'
          - `bucket` string, required
          - `region` string, required
          - `access_key` string, required
          - `secret_access_key` string, required
          - `file_type` 'ogg' | 'mp4'
        - GcpRecordingLocation: GCP recording location provider.
          - `provider` 'gcp'
          - `bucket` string, required
          - `credentials` string, required
          - `file_type` 'ogg' | 'mp4'
        - AzureRecordingLocation: Azure recording location provider.
          - `provider` 'azure'
          - `container_name` string, required
          - `account_name` string, required
          - `account_key` string, required
          - `file_type` 'ogg' | 'mp4'
    - `pre_conversation_workflow` WorkflowAction
      - `id` string, nullable: Name of this action's result in steps (steps.<id>). Defaults to its position counted from 1: steps.1, steps.2, … and steps.on_error_1, … in Actions on Error
      - `type` 'workflow'
      - `workflow_id` string, nullable
      - `workflow_version` integer, nullable
      - `initial_step` string, nullable
      - `definition` object, nullable: Inline workflow definition (mutually exclusive with workflow_id/workflow_version)
      - `condition` string, nullable
      - `reject_on_error` boolean: If true, abort the conversation when the workflow fails
      - `verifies_identity` VerifiesIdentity: Marks a workflow as an identity verifier: the only writer of ``verified_identities``; its ``custom_metadata`` output is never saved.
        - `subject_type` string, required
        - `output_schema` object, nullable: JSON Schema checked against {"identifiers": ..., "attributes": ...}
        - `ttl` integer: How long a verification stays valid; at most 7 days
        - `lockout` VerifierLockout: Per-conversation limit on a verifier tool's rejected attempts.
          - `max_failures` integer: Rejected tool-trigger attempts in one conversation before the verifier locks
      - `input_exclude` string[], nullable: Conversation parts left out of the workflow's input: agent_instructions, transcript, tool_outputs, transcript_internals. Unset sends everything
      - `include_page_view` boolean: Web chat with page context: add what was on the visitor's screen when they sent their latest message (conversation_metadata.page_view), if it was read. Off sends only where each message was sent from.
    - `identity_verifiers` IdentityVerifier[]
      - `id` string, nullable: Name of this action's result in steps (steps.<id>). Defaults to its position counted from 1: steps.1, steps.2, … and steps.on_error_1, … in Actions on Error
      - `type` 'workflow'
      - `workflow_id` string, nullable
      - `workflow_version` integer, nullable
      - `initial_step` string, nullable
      - `definition` object, nullable: Inline workflow definition (mutually exclusive with workflow_id/workflow_version)
      - `condition` string, nullable
      - `reject_on_error` boolean: If true, abort the conversation when the workflow fails
      - `verifies_identity` VerifiesIdentity: Marks a workflow as an identity verifier: the only writer of ``verified_identities``; its ``custom_metadata`` output is never saved.
        - `subject_type` string, required
        - `output_schema` object, nullable: JSON Schema checked against {"identifiers": ..., "attributes": ...}
        - `ttl` integer: How long a verification stays valid; at most 7 days
        - `lockout` VerifierLockout: Per-conversation limit on a verifier tool's rejected attempts.
          - `max_failures` integer: Rejected tool-trigger attempts in one conversation before the verifier locks
      - `input_exclude` string[], nullable: Conversation parts left out of the workflow's input: agent_instructions, transcript, tool_outputs, transcript_internals. Unset sends everything
      - `include_page_view` boolean: Web chat with page context: add what was on the visitor's screen when they sent their latest message (conversation_metadata.page_view), if it was read. Off sends only where each message was sent from.
      - `timing` 'background' | 'before_start': Conversation start only: before the greeting, or alongside it
    - `post_conversation_workflow` WorkflowAction
      - `id` string, nullable: Name of this action's result in steps (steps.<id>). Defaults to its position counted from 1: steps.1, steps.2, … and steps.on_error_1, … in Actions on Error
      - `type` 'workflow'
      - `workflow_id` string, nullable
      - `workflow_version` integer, nullable
      - `initial_step` string, nullable
      - `definition` object, nullable: Inline workflow definition (mutually exclusive with workflow_id/workflow_version)
      - `condition` string, nullable
      - `reject_on_error` boolean: If true, abort the conversation when the workflow fails
      - `verifies_identity` VerifiesIdentity: Marks a workflow as an identity verifier: the only writer of ``verified_identities``; its ``custom_metadata`` output is never saved.
        - `subject_type` string, required
        - `output_schema` object, nullable: JSON Schema checked against {"identifiers": ..., "attributes": ...}
        - `ttl` integer: How long a verification stays valid; at most 7 days
        - `lockout` VerifierLockout: Per-conversation limit on a verifier tool's rejected attempts.
          - `max_failures` integer: Rejected tool-trigger attempts in one conversation before the verifier locks
      - `input_exclude` string[], nullable: Conversation parts left out of the workflow's input: agent_instructions, transcript, tool_outputs, transcript_internals. Unset sends everything
      - `include_page_view` boolean: Web chat with page context: add what was on the visitor's screen when they sent their latest message (conversation_metadata.page_view), if it was read. Off sends only where each message was sent from.
    - `extraction_workflow` WorkflowAction
      - `id` string, nullable: Name of this action's result in steps (steps.<id>). Defaults to its position counted from 1: steps.1, steps.2, … and steps.on_error_1, … in Actions on Error
      - `type` 'workflow'
      - `workflow_id` string, nullable
      - `workflow_version` integer, nullable
      - `initial_step` string, nullable
      - `definition` object, nullable: Inline workflow definition (mutually exclusive with workflow_id/workflow_version)
      - `condition` string, nullable
      - `reject_on_error` boolean: If true, abort the conversation when the workflow fails
      - `verifies_identity` VerifiesIdentity: Marks a workflow as an identity verifier: the only writer of ``verified_identities``; its ``custom_metadata`` output is never saved.
        - `subject_type` string, required
        - `output_schema` object, nullable: JSON Schema checked against {"identifiers": ..., "attributes": ...}
        - `ttl` integer: How long a verification stays valid; at most 7 days
        - `lockout` VerifierLockout: Per-conversation limit on a verifier tool's rejected attempts.
          - `max_failures` integer: Rejected tool-trigger attempts in one conversation before the verifier locks
      - `input_exclude` string[], nullable: Conversation parts left out of the workflow's input: agent_instructions, transcript, tool_outputs, transcript_internals. Unset sends everything
      - `include_page_view` boolean: Web chat with page context: add what was on the visitor's screen when they sent their latest message (conversation_metadata.page_view), if it was read. Off sends only where each message was sent from.
    - `on_idle` OnIdleConfig: Where a quiet conversation is filed in the helpdesk (the idle archive). The same actions, conditions and ``steps`` as a tool, with no ``arguments``: no model called anything. Workflows run in order, and the first ``handoff`` whose condition holds and which has a connection on this channel is the route; nothing after it runs. None holding means the conversation is not filed. Of a ``handoff`` only the route and the condition apply: an archive says nothing to the customer. See ``services/handoff_archive.py``.
      - `actions` union[], required
        - union
          - WorkflowAction
            - `id` string, nullable: Name of this action's result in steps (steps.<id>). Defaults to its position counted from 1: steps.1, steps.2, … and steps.on_error_1, … in Actions on Error
            - `type` 'workflow'
            - `workflow_id` string, nullable
            - `workflow_version` integer, nullable
            - `initial_step` string, nullable
            - `definition` object, nullable: Inline workflow definition (mutually exclusive with workflow_id/workflow_version)
            - `condition` string, nullable
            - `reject_on_error` boolean: If true, abort the conversation when the workflow fails
            - `verifies_identity` VerifiesIdentity: Marks a workflow as an identity verifier: the only writer of ``verified_identities``; its ``custom_metadata`` output is never saved.
              - …
            - `input_exclude` string[], nullable: Conversation parts left out of the workflow's input: agent_instructions, transcript, tool_outputs, transcript_internals. Unset sends everything
            - `include_page_view` boolean: Web chat with page context: add what was on the visitor's screen when they sent their latest message (conversation_metadata.page_view), if it was read. Off sends only where each message was sent from.
          - HandoffAction: Hand this conversation to a human, as an action on any config tool. This is the ONLY way to define an escalation — there is no built-in handoff tool, exactly as there is no built-in cold transfer. The reason is routing: with Gladly the entry point + group IS the queue, so a support desk with billing / returns / technical needs one rule each. Domain-named tools (``escalate_billing_dispute``) also make the model markedly better at choosing than one generic tool plus a free-text reason. Everything here is per-ROUTE. Everything that belongs to the helpdesk rather than the route — the credentials, how long it takes to answer, what context it wants — lives on the ``handoff_connections`` row and is folded in by ``handoff_intent.resolve_handoff_action_config``. The split test is simply whether the value depends on the helpdesk or on this particular escalation. Customer-facing copy is deliberately here and not on the connection: it is spoken in the agent's voice and language, and one connection is shared by every agent in the org. ``custom_channel_id`` is rarely set. In Gladly a custom channel CONTAINS entry points, so queues under one channel share its id and only the entry point address varies; it exists for the one-channel-per-queue topology. The route fields above are the DEFAULT route. ``channels`` overrides them per channel, because a helpdesk generally exposes chat and email as separate integrations: same escalation, same description the model routes on, two destinations. Nothing else is per-channel — the copy is spoken in the agent's voice regardless of the medium. Like every other action this records INTENT and returns; the runtime opens the handoff at its own boundary. Nothing here raises across layers.
            - `id` string, nullable: Name of this action's result in steps (steps.<id>). Defaults to its position counted from 1: steps.1, steps.2, … and steps.on_error_1, … in Actions on Error
            - `type` 'handoff'
            - `connection_id` string, nullable: handoff_connections row to escalate through — the helpdesk, its credentials and its timers.
            - `entry_point_address` string, nullable: Provider-side inbox this escalation lands in (Gladly: the Entry Point 'Address'). This is the queue.
            - `custom_channel_id` string, nullable: Usually blank. Entry points under one custom channel share its id; set this only if this queue is on a different channel.
            - `group_id` string, nullable: Optional message-group id, sent alongside the entry point.
            - `group_name` string, nullable: Optional message-group name, sent alongside group_id.
            - `channels` object, nullable: Override where this rule lands, per channel. One helpdesk usually needs a different connection for chat than for email, and this is the same rule either way — same name, same description, same copy. Only the channels with a relay can escalate at all (see handoff_intent.RELAY_CHANNELS); an entry for any other is inert.
            - `handoff_message` string, nullable: Deterministic reply sent the moment this handoff opens (the model never paraphrases it).
            - `send_handoff_message` boolean, nullable: EMAIL ONLY. Turn off to open the escalation silently: the helpdesk gets the conversation and the customer's next mail is the human's reply, with no 'connecting you' message in between. Some teams read that message as a second automated reply to an inbox that already auto-acknowledged. On chat and voice the copy is always sent — silence there reads as the agent having stopped answering.
            - `waiting_message` string, nullable: Posted when no human has replied within the connection's no-reply window.
            - `unavailable_message` string, nullable: Said to the customer when this handoff could not be opened. The bot must never promise a human it cannot reach.
            - `on_unavailable` 'stay_with_bot' | 'end', nullable: What to do when the handoff cannot be opened.
            - `require_identity` 'off' | 'email' | 'phone' | 'either', nullable: Contact details this escalation must have before it opens. When the conversation does not already contain them, the customer is shown a form and the handoff opens on submit. 'off' (the default) escalates immediately.
            - `identity_message` string, nullable: Said to the customer when the form is shown, in place of the handoff message.
            - `identity_form_title` string, nullable: Heading on the form shown to the customer.
            - `include_attachments` boolean, nullable: Forward the photos and files the customer shared with the agent to the human, and note them in the pushed transcript. Off by default: an escalation about a late order should not carry the ID photo someone uploaded earlier to verify a discount. A message that was nothing but attachments is still noted as such when off, or it would vanish from the transcript entirely.
            - `condition` string, nullable
    - `scorecard_ids` string[], nullable
    - `voicemail_detection` VoicemailDetectionConfig: Configuration for voicemail detection and response behavior.
      - `beep_detection_threshold` number, required: Confidence threshold (0.0-1.0) for background beep detection. Having above 0.95 yields better result
      - `beep_detection_timeout` integer, required: Max seconds to run background beep detection
      - `tool_call_overrides` VoicemailToolCallOverrides: Overrides for voicemail tool-call based detection and prompt handling. If not provided, then the default values will be used. beep_detection_threshold: 0.9 beep_detection_timeout: 10 name: voicemail_detected description: whenever you hear 'you have reached a voicemail'
        - `beep_detection_threshold` number: Confidence threshold (0.0-1.0) for beep after voice prompt
        - `beep_detection_timeout` integer: Max seconds to wait for beep after voicemail voice prompt
        - `name` string: Tool call function name the LLM can invoke (e.g., 'voicemail_detected')
        - `description` string: Description/when to invoke the tool (e.g., when voicemail greeting is detected)
      - `play_message` VoicemailPlayMessageAction
        - `message` string, required
        - `generate` boolean
    - `inactivity_nudge` InactivityNudgeConfig: Nudge the user on silence, then hang up after the nudges go unanswered. Voice only (telephony + WebRTC voice). Driven by LiveKit's VAD-based ``away`` user state, so it has no effect on text/chat conversations. Always on — there is no enable/disable toggle; tune the timings and nudge copy instead.
      - `timeout_seconds` number: Silence before the first nudge (feeds LiveKit user_away_timeout).
      - `nudge_interval_seconds` number: Delay between nudges, and the final grace period before hangup.
      - `nudges` union[]: Ordered, one per attempt (escalating copy). Hang up after the last.
        - union
          - NudgeStaticMessage: Inactivity nudge spoken as exact text (verbatim via TTS).
            - `type` 'bot_initiates_static'
            - `text` string, required
          - NudgeDynamicMessage: Inactivity nudge generated by the LLM from instructions.
            - `type` 'bot_initiates_dynamic'
            - `prompt` string, required
    - `screen_pop` object, nullable: Named links that /<org>/screen-pop/<caller>?target=<name> opens for a human taking over a call from this agent.
  - `published` boolean, required
  - `instruction_builder_inputs` object, nullable
  - `trained_instruction_builder_inputs` object, nullable
  - `generation_timestamp` string, date-time, nullable
  - `manually_edited` boolean
  - `is_updated` boolean
  - `cache_fingerprints` string[]
  - `created_at` string, date-time, required
  - `updated_at` string, date-time, required
  - `created_by` string, nullable
  - `modified_by` string, nullable

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/anyreach/apis/anyreach-api.md) - [All operations](https://skmtc.dev/anyreach/apis/anyreach-api/llms.txt) - [OpenAPI document](https://skmtc.dev/anyreach/apis/anyreach-api/revisions/2eddf40c8c93?raw)
