{
  "asyncapi": "3.1.0",
  "info": {
    "title": "Dialt Realtime API",
    "version": "1.0.0",
    "description": "Use the Realtime API for live voice and text conversations. Connect to wss://api.dialt.com/v1/realtime and send a start message with your session key. Wait for ready before sending audio or text. The browser and Python SDKs handle this connection sequence for you. For auth probes, post-session feedback and client failure reports, open a separate connection and send auth, feedback or client_error as the first frame instead of start. These requests return ok or error and end the connection without a ready event."
  },
  "servers": {
    "production": {
      "host": "api.dialt.com",
      "protocol": "wss",
      "description": "Production realtime endpoint. A bearer handshake header may carry the API key instead of the start frame.",
      "security": [
        {
          "$ref": "#/components/securitySchemes/bearerAuth"
        }
      ]
    }
  },
  "channels": {
    "conversation": {
      "address": "/v1/realtime",
      "messages": {
        "requestReply": {
          "$ref": "#/components/messages/requestReply"
        },
        "replyAck": {
          "$ref": "#/components/messages/replyAck"
        },
        "responseDone": {
          "$ref": "#/components/messages/responseDone"
        },
        "interruptAck": {
          "$ref": "#/components/messages/interruptAck"
        },
        "conversationSnapshot": {
          "$ref": "#/components/messages/conversationSnapshot"
        },
        "conversationItem": {
          "$ref": "#/components/messages/conversationItem"
        },
        "speechStarted": {
          "$ref": "#/components/messages/speechStarted"
        },
        "speechStopped": {
          "$ref": "#/components/messages/speechStopped"
        },
        "start": {
          "$ref": "#/components/messages/start"
        },
        "inputText": {
          "$ref": "#/components/messages/inputText"
        },
        "setVoice": {
          "$ref": "#/components/messages/setVoice"
        },
        "setInstructions": {
          "$ref": "#/components/messages/setInstructions"
        },
        "continueWithAgent": {
          "$ref": "#/components/messages/handoffAgent"
        },
        "toolResult": {
          "$ref": "#/components/messages/toolResult"
        },
        "toolDeferred": {
          "$ref": "#/components/messages/toolDeferred"
        },
        "toolProgress": {
          "$ref": "#/components/messages/toolProgress"
        },
        "toolPartialResult": {
          "$ref": "#/components/messages/toolPartialResult"
        },
        "toolInteractionUpdate": {
          "$ref": "#/components/messages/toolInteractionUpdate"
        },
        "toolCancelClient": {
          "$ref": "#/components/messages/toolCancelClient"
        },
        "playbackStopped": {
          "$ref": "#/components/messages/playbackStopped"
        },
        "playbackReport": {
          "$ref": "#/components/messages/playbackReport"
        },
        "webrtcOffer": {
          "$ref": "#/components/messages/webrtcOffer"
        },
        "webrtcCandidate": {
          "$ref": "#/components/messages/webrtcCandidate"
        },
        "webrtcIceComplete": {
          "$ref": "#/components/messages/webrtcIceComplete"
        },
        "microphoneAudio": {
          "$ref": "#/components/messages/microphoneAudio"
        },
        "ready": {
          "$ref": "#/components/messages/ready"
        },
        "silentMic": {
          "$ref": "#/components/messages/silentMic"
        },
        "turn": {
          "$ref": "#/components/messages/turn"
        },
        "asr": {
          "$ref": "#/components/messages/asr"
        },
        "injectContextAck": {
          "$ref": "#/components/messages/injectContextAck"
        },
        "handoffAck": {
          "$ref": "#/components/messages/handoffAck"
        },
        "utterance": {
          "$ref": "#/components/messages/utterance"
        },
        "done": {
          "$ref": "#/components/messages/done"
        },
        "sessionEndRequested": {
          "$ref": "#/components/messages/sessionEndRequested"
        },
        "policyFlag": {
          "$ref": "#/components/messages/policyFlag"
        },
        "interrupted": {
          "$ref": "#/components/messages/interrupted"
        },
        "canceled": {
          "$ref": "#/components/messages/canceled"
        },
        "toolCall": {
          "$ref": "#/components/messages/toolCall"
        },
        "toolDeferredAck": {
          "$ref": "#/components/messages/toolDeferredAck"
        },
        "toolDeferredResume": {
          "$ref": "#/components/messages/toolDeferredResume"
        },
        "toolCancelServer": {
          "$ref": "#/components/messages/toolCancelServer"
        },
        "toolJobDeliveryFailed": {
          "$ref": "#/components/messages/toolJobDeliveryFailed"
        },
        "toolJobNarration": {
          "$ref": "#/components/messages/toolJobNarration"
        },
        "working": {
          "$ref": "#/components/messages/working"
        },
        "toolInteractionUpdateAck": {
          "$ref": "#/components/messages/toolInteractionUpdateAck"
        },
        "voiceChanged": {
          "$ref": "#/components/messages/voiceChanged"
        },
        "error": {
          "$ref": "#/components/messages/error"
        },
        "assistantAudio": {
          "$ref": "#/components/messages/assistantAudio"
        },
        "webrtcIce": {
          "$ref": "#/components/messages/webrtcIce"
        },
        "webrtcAnswer": {
          "$ref": "#/components/messages/webrtcAnswer"
        },
        "webrtcBye": {
          "$ref": "#/components/messages/webrtcBye"
        },
        "interrupt": {
          "$ref": "#/components/messages/interrupt"
        },
        "setTools": {
          "$ref": "#/components/messages/setTools"
        },
        "setToolChoice": {
          "$ref": "#/components/messages/setToolChoice"
        },
        "triggerReply": {
          "$ref": "#/components/messages/triggerReply"
        },
        "injectContext": {
          "$ref": "#/components/messages/injectContext"
        },
        "textDelta": {
          "$ref": "#/components/messages/textDelta"
        },
        "permissionPending": {
          "$ref": "#/components/messages/permissionPending"
        },
        "permissionApproved": {
          "$ref": "#/components/messages/permissionApproved"
        },
        "permissionRefused": {
          "$ref": "#/components/messages/permissionRefused"
        },
        "sessionClosingSoon": {
          "$ref": "#/components/messages/sessionClosingSoon"
        },
        "lowCreditWarning": {
          "$ref": "#/components/messages/lowCreditWarning"
        },
        "playbackPause": {
          "$ref": "#/components/messages/playbackPause"
        },
        "playbackResume": {
          "$ref": "#/components/messages/playbackResume"
        },
        "playbackPaused": {
          "$ref": "#/components/messages/playbackPaused"
        },
        "permissionExpired": {
          "$ref": "#/components/messages/permissionExpired"
        },
        "resolveToolPermission": {
          "$ref": "#/components/messages/resolveToolPermission"
        },
        "permissionResolution": {
          "$ref": "#/components/messages/permissionResolution"
        },
        "policyError": {
          "$ref": "#/components/messages/policyError"
        },
        "policyCheck": {
          "$ref": "#/components/messages/policyCheck"
        },
        "policySettled": {
          "$ref": "#/components/messages/policySettled"
        },
        "wrapUp": {
          "$ref": "#/components/messages/wrapUp"
        },
        "inputAudioDone": {
          "$ref": "#/components/messages/inputAudioDone"
        },
        "asrCorrection": {
          "$ref": "#/components/messages/asrCorrection"
        },
        "auth": {
          "$ref": "#/components/messages/auth"
        },
        "feedback": {
          "$ref": "#/components/messages/feedback"
        },
        "clientError": {
          "$ref": "#/components/messages/clientError"
        },
        "ok": {
          "$ref": "#/components/messages/ok"
        }
      },
      "bindings": {
        "ws": {
          "method": "GET"
        }
      }
    }
  },
  "operations": {
    "sendClientMessages": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/conversation"
      },
      "summary": "Send the start frame, microphone audio, and client controls",
      "messages": [
        {
          "$ref": "#/channels/conversation/messages/requestReply"
        },
        {
          "$ref": "#/channels/conversation/messages/start"
        },
        {
          "$ref": "#/channels/conversation/messages/inputText"
        },
        {
          "$ref": "#/channels/conversation/messages/setVoice"
        },
        {
          "$ref": "#/channels/conversation/messages/continueWithAgent"
        },
        {
          "$ref": "#/channels/conversation/messages/toolDeferred"
        },
        {
          "$ref": "#/channels/conversation/messages/toolResult"
        },
        {
          "$ref": "#/channels/conversation/messages/toolProgress"
        },
        {
          "$ref": "#/channels/conversation/messages/toolPartialResult"
        },
        {
          "$ref": "#/channels/conversation/messages/toolInteractionUpdate"
        },
        {
          "$ref": "#/channels/conversation/messages/toolCancelClient"
        },
        {
          "$ref": "#/channels/conversation/messages/playbackStopped"
        },
        {
          "$ref": "#/channels/conversation/messages/playbackReport"
        },
        {
          "$ref": "#/channels/conversation/messages/microphoneAudio"
        },
        {
          "$ref": "#/channels/conversation/messages/webrtcOffer"
        },
        {
          "$ref": "#/channels/conversation/messages/webrtcCandidate"
        },
        {
          "$ref": "#/channels/conversation/messages/webrtcIceComplete"
        },
        {
          "$ref": "#/channels/conversation/messages/interrupt"
        },
        {
          "$ref": "#/channels/conversation/messages/setTools"
        },
        {
          "$ref": "#/channels/conversation/messages/setToolChoice"
        },
        {
          "$ref": "#/channels/conversation/messages/triggerReply"
        },
        {
          "$ref": "#/channels/conversation/messages/injectContext"
        },
        {
          "$ref": "#/channels/conversation/messages/playbackPaused"
        },
        {
          "$ref": "#/channels/conversation/messages/resolveToolPermission"
        },
        {
          "$ref": "#/channels/conversation/messages/setInstructions"
        },
        {
          "$ref": "#/channels/conversation/messages/wrapUp"
        },
        {
          "$ref": "#/channels/conversation/messages/inputAudioDone"
        }
      ]
    },
    "receiveServerMessages": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/conversation"
      },
      "summary": "Receive lifecycle events, assistant audio, transcripts, and tool calls",
      "messages": [
        {
          "$ref": "#/channels/conversation/messages/replyAck"
        },
        {
          "$ref": "#/channels/conversation/messages/responseDone"
        },
        {
          "$ref": "#/channels/conversation/messages/interruptAck"
        },
        {
          "$ref": "#/channels/conversation/messages/conversationSnapshot"
        },
        {
          "$ref": "#/channels/conversation/messages/conversationItem"
        },
        {
          "$ref": "#/channels/conversation/messages/speechStarted"
        },
        {
          "$ref": "#/channels/conversation/messages/speechStopped"
        },
        {
          "$ref": "#/channels/conversation/messages/ready"
        },
        {
          "$ref": "#/channels/conversation/messages/silentMic"
        },
        {
          "$ref": "#/channels/conversation/messages/turn"
        },
        {
          "$ref": "#/channels/conversation/messages/asr"
        },
        {
          "$ref": "#/channels/conversation/messages/utterance"
        },
        {
          "$ref": "#/channels/conversation/messages/injectContextAck"
        },
        {
          "$ref": "#/channels/conversation/messages/handoffAck"
        },
        {
          "$ref": "#/channels/conversation/messages/done"
        },
        {
          "$ref": "#/channels/conversation/messages/sessionEndRequested"
        },
        {
          "$ref": "#/channels/conversation/messages/interrupted"
        },
        {
          "$ref": "#/channels/conversation/messages/canceled"
        },
        {
          "$ref": "#/channels/conversation/messages/toolDeferredAck"
        },
        {
          "$ref": "#/channels/conversation/messages/toolDeferredResume"
        },
        {
          "$ref": "#/channels/conversation/messages/toolCall"
        },
        {
          "$ref": "#/channels/conversation/messages/toolCancelServer"
        },
        {
          "$ref": "#/channels/conversation/messages/toolJobDeliveryFailed"
        },
        {
          "$ref": "#/channels/conversation/messages/toolJobNarration"
        },
        {
          "$ref": "#/channels/conversation/messages/working"
        },
        {
          "$ref": "#/channels/conversation/messages/toolInteractionUpdateAck"
        },
        {
          "$ref": "#/channels/conversation/messages/voiceChanged"
        },
        {
          "$ref": "#/channels/conversation/messages/error"
        },
        {
          "$ref": "#/channels/conversation/messages/assistantAudio"
        },
        {
          "$ref": "#/channels/conversation/messages/webrtcIce"
        },
        {
          "$ref": "#/channels/conversation/messages/webrtcAnswer"
        },
        {
          "$ref": "#/channels/conversation/messages/webrtcBye"
        },
        {
          "$ref": "#/channels/conversation/messages/textDelta"
        },
        {
          "$ref": "#/channels/conversation/messages/permissionPending"
        },
        {
          "$ref": "#/channels/conversation/messages/permissionApproved"
        },
        {
          "$ref": "#/channels/conversation/messages/permissionRefused"
        },
        {
          "$ref": "#/channels/conversation/messages/sessionClosingSoon"
        },
        {
          "$ref": "#/channels/conversation/messages/lowCreditWarning"
        },
        {
          "$ref": "#/channels/conversation/messages/playbackPause"
        },
        {
          "$ref": "#/channels/conversation/messages/playbackResume"
        },
        {
          "$ref": "#/channels/conversation/messages/permissionExpired"
        },
        {
          "$ref": "#/channels/conversation/messages/permissionResolution"
        },
        {
          "$ref": "#/channels/conversation/messages/policyFlag"
        },
        {
          "$ref": "#/channels/conversation/messages/policyError"
        },
        {
          "$ref": "#/channels/conversation/messages/policyCheck"
        },
        {
          "$ref": "#/channels/conversation/messages/policySettled"
        },
        {
          "$ref": "#/channels/conversation/messages/asrCorrection"
        }
      ]
    },
    "sendHandoffAgent": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/conversation"
      },
      "summary": "Request one atomic agent handoff at a safe boundary",
      "messages": [
        {
          "$ref": "#/channels/conversation/messages/continueWithAgent"
        }
      ]
    },
    "receiveHandoffAck": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/conversation"
      },
      "summary": "Receive the correlated handoff acknowledgement",
      "messages": [
        {
          "$ref": "#/channels/conversation/messages/handoffAck"
        }
      ]
    },
    "sendOneShotRequest": {
      "action": "send",
      "channel": {
        "$ref": "#/channels/conversation"
      },
      "summary": "Send one authenticated auth, feedback or client-error request as the first frame on a fresh connection, instead of start",
      "messages": [
        {
          "$ref": "#/channels/conversation/messages/auth"
        },
        {
          "$ref": "#/channels/conversation/messages/feedback"
        },
        {
          "$ref": "#/channels/conversation/messages/clientError"
        }
      ]
    },
    "receiveOneShotResponse": {
      "action": "receive",
      "channel": {
        "$ref": "#/channels/conversation"
      },
      "summary": "Receive ok or error for a one-shot request; this connection does not become a live conversation",
      "messages": [
        {
          "$ref": "#/channels/conversation/messages/ok"
        },
        {
          "$ref": "#/channels/conversation/messages/error"
        }
      ]
    }
  },
  "components": {
    "messages": {
      "requestReply": {
        "name": "requestReply",
        "contentType": "application/json",
        "description": "Requires request_reply_v1. FIFO capacity 16, lifetime receipt budget 256. Same ID and payload recovers latest state, including after resume. No implicit user message or replay under a new ID.",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "operation_id"
          ],
          "properties": {
            "type": {
              "const": "request_reply"
            },
            "operation_id": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128
            },
            "instructions": {
              "type": "string",
              "minLength": 1
            }
          },
          "additionalProperties": false
        }
      },
      "replyAck": {
        "name": "replyAck",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "operation_id",
            "accepted",
            "status"
          ],
          "properties": {
            "type": {
              "const": "reply_ack"
            },
            "operation_id": {
              "type": "string"
            },
            "accepted": {
              "type": "boolean"
            },
            "status": {
              "enum": [
                "queued",
                "started",
                "completed",
                "cancelled",
                "failed",
                "rejected"
              ]
            },
            "response_id": {
              "type": "string"
            },
            "reason": {
              "type": "string"
            }
          },
          "additionalProperties": false
        }
      },
      "responseDone": {
        "name": "responseDone",
        "contentType": "application/json",
        "description": "Emitted once at logical response teardown across all segments, with status completed, cancelled or failed. May precede the final done segment fence. This is generation completion, not proof of playback; preserve queued audio and wait for each segment's done fence.",
        "payload": {
          "type": "object",
          "required": ["type", "response_id", "status"],
          "properties": {
            "type": {"const": "response_done"},
            "response_id": {"type": "string", "minLength": 1},
            "status": {"enum": ["completed", "cancelled", "failed"]}
          },
          "additionalProperties": false
        }
      },
      "interruptAck": {
        "name": "interruptAck",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "operation_id",
            "accepted",
            "status"
          ],
          "properties": {
            "type": {
              "const": "interrupt_ack"
            },
            "operation_id": {
              "type": "string"
            },
            "accepted": {
              "type": "boolean"
            },
            "status": {
              "enum": [
                "interrupted",
                "cancelled",
                "stale",
                "rejected"
              ]
            },
            "response_id": {
              "type": "string"
            },
            "target_operation_id": {
              "type": "string"
            },
            "reason": {
              "type": "string"
            }
          },
          "additionalProperties": false
        }
      },
      "conversationSnapshot": {
        "name": "conversationSnapshot",
        "contentType": "application/json",
        "description": "After ready on fresh connection and resume. Atomically replaces the read-only retained window, at most 512 items and 512 KiB compact UTF-8 JSON.",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "items",
            "first_index"
          ],
          "properties": {
            "type": {
              "const": "conversation_snapshot"
            },
            "items": {
              "type": "array",
              "maxItems": 512,
              "items": {
                "$ref": "#/components/schemas/ConversationItem"
              }
            },
            "first_index": {
              "type": "integer",
              "minimum": 0
            }
          },
          "additionalProperties": false
        }
      },
      "conversationItem": {
        "name": "conversationItem",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "item",
            "index",
            "first_index"
          ],
          "properties": {
            "type": {
              "const": "conversation_item"
            },
            "item": {
              "$ref": "#/components/schemas/ConversationItem"
            },
            "index": {
              "type": "integer",
              "minimum": 0
            },
            "first_index": {
              "type": "integer",
              "minimum": 0
            },
            "response_id": {
              "type": "string"
            },
            "turn_id": {
              "type": "string"
            }
          },
          "additionalProperties": false
        }
      },
      "speechStarted": {
        "name": "speechStarted",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "speech_id",
            "timestamp_ms"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "speech_started"
            },
            "speech_id": {
              "type": "string",
              "minLength": 1
            },
            "timestamp_ms": {
              "type": "number",
              "minimum": 0
            }
          },
          "additionalProperties": false
        }
      },
      "speechStopped": {
        "name": "speechStopped",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "speech_id",
            "timestamp_ms"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "speech_stopped"
            },
            "speech_id": {
              "type": "string",
              "minLength": 1
            },
            "timestamp_ms": {
              "type": "number",
              "minimum": 0
            },
            "reason": {
              "type": "string"
            }
          },
          "additionalProperties": false
        }
      },
      "start": {
        "name": "start",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "session_id",
            "mode"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "start"
            },
            "session_id": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-][A-Za-z0-9._-]{0,63}$",
              "minLength": 1,
              "maxLength": 64
            },
            "initial_history": {
              "type": "array",
              "maxItems": 256,
              "items": {
                "$ref": "#/components/schemas/ImportedConversationItem"
              },
              "description": "Completed ordered history, atomically validated before ready. At most 128 KiB compact UTF-8 JSON, 16 KiB per item. Unique item/call IDs; every call has exactly one later result. Nonempty history is forbidden on resume. Import never executes tools or requests a reply."
            },
            "api_key": {
              "type": "string",
              "description": "Persistent or scoped key. Optional when supplied in the WebSocket Authorization header."
            },
            "audio": {
              "$ref": "#/components/schemas/AudioConfig"
            },
            "client": {
              "$ref": "#/components/schemas/ClientInfo"
            },
            "mode": {
              "$ref": "#/components/schemas/Mode"
            },
            "transport": {
              "$ref": "#/components/schemas/TransportConfig"
            },
            "resume_token": {
              "type": "string",
              "description": "A token from a previous session's ready frame. When it matches a still-live, unexpired stash from that session's disconnect, this session resumes its conversation context (and voice/tools/instructions, where this start frame doesn't already override them) instead of starting empty. An expired, unknown, consumed, or cross-account token is rejected with a non-retryable resume_failed error; callers may then deliberately start fresh without the token. Single-use: each resume consumes the stash and this session's own ready frame carries a new token."
            }
          },
          "additionalProperties": false,
          "allOf": [
            {
              "if": {
                "properties": {
                  "mode": {
                    "properties": {
                      "modality": {
                        "const": "text"
                      }
                    },
                    "required": [
                      "modality"
                    ]
                  }
                }
              },
              "then": {
                "not": {
                  "required": [
                    "audio"
                  ]
                }
              },
              "else": {
                "required": [
                  "audio"
                ]
              }
            }
          ]
        },
        "description": "Opens a conversation. api_key may be omitted when the WebSocket handshake supplied Authorization: Bearer <key>. Voice mode requires audio; text mode omits it and requires the WebSocket transport."
      },
      "inputText": {
        "name": "inputText",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "text"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "input_text"
            },
            "text": {
              "type": "string",
              "minLength": 1,
              "maxLength": 20000,
              "description": "One committed user turn. Accepted only when mode.modality is text."
            }
          },
          "additionalProperties": false
        },
        "description": "Commit a user turn in text mode. Dialt emits the ordinary asr, turn, text_delta, utterance, and done events without audio frames."
      },
      "setVoice": {
        "name": "setVoice",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "voice"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "set_voice"
            },
            "voice": {
              "type": "string",
              "minLength": 1
            }
          },
          "additionalProperties": false
        },
        "description": "Select a current stock voice key from GET /v1/voices for the next reply. Empty, unknown, retired or unavailable keys emit invalid_voice and leave the current voice unchanged. In voice sessions, changing the listening route or language hints also emits invalid_voice and requires a new call. Text sessions may change language. A voice event confirms every accepted selection, including the active key."
      },
      "setInstructions": {
        "name": "setInstructions",
        "summary": "Replace the session instructions (start.mode.instructions) mid-session; applies from the next reply and re-stamps prompt_config. new_speaker: true remains available for an independent speaker change and folds the call so far into one prior-agent transcript. With new_speaker the frame is refused while a reply is in flight (error code instructions_busy, retryable): send it after done.",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "instructions"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "set_instructions"
            },
            "instructions": {
              "type": "string",
              "minLength": 1,
              "pattern": "\\S",
              "description": "Replacement instructions, same layer as start's mode.instructions; capped like start. Must be non-empty: blank text is rejected with an error frame (code invalid_instructions) and changes nothing."
            },
            "new_speaker": {
              "type": "boolean",
              "default": false,
              "description": "A different agent takes the call from here: fold the conversation so far into a prior-agent transcript."
            }
          },
          "additionalProperties": false
        }
      },
      "handoffAgent": {
        "name": "continueWithAgent",
        "summary": "Atomically continue the conversation with an initialized replacement agent at a safe boundary. One continuation may be pending. The broker sends queued, then applied or rejected within 10 seconds. It does not retry, survive reconnect, or start specialist speech. The former handoff_agent type remains a compatibility alias.",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "operation_id",
            "instructions",
            "tools"
          ],
          "properties": {
            "type": {
              "type": "string",
              "enum": [
                "continue_with_agent",
                "handoff_agent"
              ]
            },
            "operation_id": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "description": "Client-generated identifier echoed by handoff_ack."
            },
            "instructions": {
              "type": "string",
              "maxLength": 64000,
              "minLength": 1,
              "pattern": "\\S",
              "description": "Replacement agent instructions: who the new agent is and what it does. Must be non-empty; blank text is rejected with a handoff_ack whose status is rejected."
            },
            "tools": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/ToolDefinition"
              },
              "description": "Replacement client tool manifest."
            },
            "voice": {
              "type": "string",
              "description": "Optional replacement stock voice key from GET /v1/voices.",
              "minLength": 1
            },
            "context": {
              "type": "string",
              "maxLength": 2000,
              "description": "Optional specialist context added with the handoff."
            },
            "tool_choice": {
              "$ref": "#/components/schemas/DialtMode/properties/tool_choice",
              "description": "Optional generation constraint validated against the replacement tools and applied with the agent."
            },
            "tool_choice_one_shot": {
              "type": "boolean",
              "default": false,
              "description": "When true, tool_choice constrains only the incoming agent's first caller- or host-requested reply, then restores auto. Requires tool_choice."
            }
          },
          "additionalProperties": false
        }
      },
      "toolResult": {
        "name": "toolResult",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "id",
            "content"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "tool_result"
            },
            "id": {
              "type": "string"
            },
            "content": {},
            "outcome": {
              "type": "string",
              "enum": [
                "succeeded",
                "failed",
                "cancelled",
                "timed_out",
                "unknown"
              ],
              "default": "unknown",
              "description": "Terminal real-world outcome. Send it whenever the host can establish it: succeeded with verified=true lets the assistant confirm the action (send it when the host holds proof, such as a booking confirmation ID); unknown makes the assistant say it cannot confirm, which suits queued or fire-and-forget actions; failed makes it say the action did not go through. Missing or invalid values normalize to unknown. Guidance: https://dialt.com/docs/api/websocket/#tool-result-outcome"
            },
            "verified": {
              "type": "boolean",
              "default": false,
              "description": "True when the host has proof the action completed, such as a confirmation ID. May be true only with outcome=succeeded; this pair lets the assistant confirm the action."
            },
            "continue_with": {
              "type": "object",
              "required": [
                "operation_id",
                "instructions",
                "tools"
              ],
              "properties": {
                "operation_id": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 128,
                  "description": "Client-generated identifier echoed by handoff_ack."
                },
                "instructions": {
                  "type": "string",
                  "maxLength": 64000,
                  "minLength": 1,
                  "pattern": "\\S",
                  "description": "Replacement agent instructions."
                },
                "tools": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ToolDefinition"
                  },
                  "description": "Replacement client tool manifest."
                },
                "voice": {
                  "type": "string",
                  "minLength": 1,
                  "description": "Optional replacement stock voice key from GET /v1/voices."
                },
                "context": {
                  "type": "string",
                  "maxLength": 2000,
                  "description": "Optional context for the new agent."
                },
                "tool_choice": {
                  "$ref": "#/components/schemas/DialtMode/properties/tool_choice",
                  "description": "Optional generation constraint validated against the replacement tools and applied before the incoming reply starts."
                },
                "tool_choice_one_shot": {
                  "type": "boolean",
                  "default": false,
                  "description": "When true, tool_choice constrains only the incoming agent's first reply, then restores auto. Requires tool_choice."
                }
              },
              "additionalProperties": false,
              "description": "Atomically end this tool turn and continue the conversation with the replacement agent. The broker validates the whole result and continuation before mutation, suppresses any not-yet-audible old-agent recorded bridge or answer, requests exactly one new-agent reply, and reports the outcome with handoff_ack."
            },
            "handoff": {
              "$ref": "#/components/messages/toolResult/payload/properties/continue_with",
              "deprecated": true,
              "description": "Deprecated compatibility alias for continue_with. A frame cannot contain both fields."
            }
          },
          "additionalProperties": false
        }
      },
      "toolDeferred": {
        "name": "toolDeferred",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "id",
            "handle"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "tool_deferred"
            },
            "id": {
              "type": "string"
            },
            "handle": {
              "type": "string",
              "minLength": 1,
              "maxLength": 200
            },
            "status_label": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            }
          },
          "additionalProperties": false
        },
        "description": "Detach an eligible call from its voice turn while preserving managed job identity."
      },
      "toolProgress": {
        "name": "toolProgress",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "id",
            "note"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "tool_progress"
            },
            "id": {
              "type": "string"
            },
            "note": {
              "type": "string",
              "maxLength": 500
            }
          },
          "additionalProperties": false
        }
      },
      "toolPartialResult": {
        "name": "toolPartialResult",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "id",
            "content"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "tool_partial_result"
            },
            "id": {
              "type": "string"
            },
            "content": {
              "description": "Structured partial result content. Capped at 2 KiB by default; at most 8 partials are accepted per call. Does not resolve the call."
            },
            "reply": {
              "type": "boolean",
              "default": false,
              "description": "Ask Dialt to narrate this milestone immediately. The partial always lands in context, but the proactive reply is skipped if another reply is already in flight. Ignored when `interaction` is given."
            },
            "interaction": {
              "type": "object",
              "description": "Marks this partial as needing a user decision rather than a routine milestone. Unlike reply:true alone, never silently dropped when busy -- falls back to the same completion-job queue tool completions use, preempting them, with a far more persistent delivery retry. See tool_job_narration for its lifecycle ack.",
              "required": [
                "prompt"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "maxLength": 64,
                  "description": "Stable, host-chosen identity for this interaction (1-64 characters), addressable later via tool_interaction_update. Single-use per session: re-raising a superseded ask means minting a fresh id. Omitted, the broker derives \"<call_id>#i<n>\" and echoes it in tool_job_narration's interaction_ids."
                },
                "prompt": {
                  "type": "string",
                  "description": "The question to ask the user, capped at 500 characters."
                },
                "options": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "A discrete set of choices (at most 8, each capped at 64 characters). Omit for a free-text answer."
                },
                "resolver": {
                  "type": "object",
                  "required": [
                    "tool"
                  ],
                  "properties": {
                    "tool": {
                      "type": "string",
                      "description": "A CLIENT-declared tool with resolver_only: true that settles this decision. The model never sees a resolver-only tool and cannot call it directly, so the caller's answer is the only way it runs. Unknown or ineligible names drop the binding (loudly) while the ask itself stands; set_tools re-validates open bindings."
                    },
                    "args": {
                      "type": "object",
                      "description": "Base arguments always passed to the resolver."
                    },
                    "option_args": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "object"
                      },
                      "description": "Per-option argument overlays. Keys must be exactly (a subset of) the offered options -- a mismatch invalidates the whole interaction. The broker constructs the final call as args + option_args[chosen option]; the model picks the option, never the arguments."
                    },
                    "answer_arg": {
                      "type": "string",
                      "description": "For free-text asks (no options): the argument name that receives the user's spoken answer. REQUIRED for a free-text binding; resolve without an answer is rejected (missing_answer)."
                    }
                  },
                  "additionalProperties": false,
                  "description": "Binds this interaction to a resolver: once the ask has been VOICED (fully or partially), every subsequent user turn's first planning round is constrained to the broker-managed interaction_transition tool (resolve / clarify / supersede / cancel) until the decision is settled -- outranking mode.tool_choice. resolve executes the bound tool with host-declared arguments through the ordinary tool_call path (execute-then-close: a failed/timed-out resolver keeps the ask open and retryable); the binding as compact JSON is capped at 2 KiB."
                }
              },
              "additionalProperties": false
            }
          },
          "additionalProperties": false
        },
        "description": "Deliver a structured segment of an in-flight tool's eventual result without resolving the call."
      },
      "toolInteractionUpdate": {
        "name": "toolInteractionUpdate",
        "summary": "Host-side close of an open interaction (docs/tool-orchestration.md, Partial results and interactions): the decision was resolved out-of-band, cancelled, or superseded by newer intent -- without completing the parent call. Stops the ask's queued or actively-speaking narration and tells the model the decision is closed. Always answered by toolInteractionUpdateAck; late/duplicate/unknown updates are rejected deterministically.",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "id",
            "interaction_id",
            "state"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "tool_interaction_update"
            },
            "id": {
              "type": "string",
              "description": "The parent call id (or deferred handle) the interaction was raised on."
            },
            "interaction_id": {
              "type": "string",
              "maxLength": 64,
              "description": "The interaction's stable identity: the host-chosen interaction.id from the raising tool_partial_result, or the broker-derived id echoed in tool_job_narration's interaction_ids."
            },
            "state": {
              "type": "string",
              "enum": [
                "resolved",
                "cancelled",
                "superseded"
              ],
              "description": "resolved: the decision was made out-of-band (e.g. in the host's own UI) and the tool is continuing. cancelled: the decision is no longer needed. superseded: replaced by newer intent (typically the user's latest instruction); raise a fresh interaction (new id) if a decision is still needed."
            },
            "note": {
              "type": "string",
              "maxLength": 200,
              "description": "Optional short human-readable reason, surfaced to the model as context (e.g. \"approved in the IDE\")."
            }
          },
          "additionalProperties": false
        }
      },
      "toolCancelClient": {
        "name": "toolCancelClient",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "id"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "tool_cancel"
            },
            "id": {
              "type": "string"
            }
          },
          "additionalProperties": false
        }
      },
      "playbackStopped": {
        "name": "playbackStopped",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "event"
          ],
          "properties": {
            "response_id": {
              "type": "string",
              "minLength": 1,
              "description": "Target discarded playback even after generation completed. Required for targeted reporting; use discarded_ms. WebRTC owns accounting and sends no duplicate client report."
            },
            "type": {
              "type": "string",
              "const": "client_event"
            },
            "event": {
              "type": "string",
              "const": "playback_stopped"
            },
            "remaining_ms": {
              "type": "number",
              "minimum": 0
            },
            "discarded_ms": {
              "type": "number",
              "minimum": 0
            },
            "barge_seq": {
              "type": "integer",
              "minimum": 0,
              "description": "Optional interruption sequence; the server uses the current sequence when omitted."
            }
          },
          "additionalProperties": false,
          "if": {
            "required": [
              "response_id"
            ]
          },
          "then": {
            "required": [
              "discarded_ms"
            ],
            "not": {
              "anyOf": [
                {
                  "required": [
                    "remaining_ms"
                  ]
                },
                {
                  "required": [
                    "barge_seq"
                  ]
                }
              ]
            }
          },
          "anyOf": [
            {
              "required": [
                "remaining_ms"
              ]
            },
            {
              "required": [
                "discarded_ms"
              ]
            }
          ]
        }
      },
      "ready": {
        "name": "ready",
        "description": "Authoritative initial session state, including selected voice and negotiated audio format, not merely an echo of start. Keep resume_token private and replace it after a successful resume; later acknowledged configuration changes update the initial state.",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "voice",
            "name",
            "voices",
            "resume_token"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "ready"
            },
            "capabilities": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "Supported conversation_v1, request_reply_v1, targeted_interrupt_v1 and speech_activity_v1. Text has no activity; WebRTC has no targeted interruption."
            },
            "active_speech": {
              "oneOf": [
                {
                  "type": "null"
                },
                {
                  "$ref": "#/components/schemas/SpeechActivity"
                }
              ],
              "description": "Null on fresh ready and resumed ready."
            },
            "modality": {
              "type": "string",
              "enum": [
                "voice",
                "text"
              ],
              "default": "voice"
            },
            "voice": {
              "type": "string"
            },
            "name": {
              "type": "string"
            },
            "voices": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/Voice"
              }
            },
            "audio": {
              "type": "object",
              "required": [
                "output_encoding",
                "output_sr"
              ],
              "properties": {
                "output_encoding": {
                  "type": "string",
                  "enum": [
                    "pcm16",
                    "pcm_f32le"
                  ]
                },
                "output_sr": {
                  "type": "integer",
                  "enum": [
                    8000,
                    16000,
                    24000
                  ]
                }
              },
              "additionalProperties": false,
              "description": "The negotiated downlink format for binary assistantAudio frames, echoing start.audio's output_encoding/output_sr (or their defaults, pcm16 at 16000, when omitted). Assert on this instead of assuming an encoding."
            },
            "resume_token": {
              "type": "string",
              "description": "Pass this back as start.resume_token on a reconnect to resume this session's conversation context if the connection drops. Short-lived (150 seconds by default), single-use and account-bound; the server stash survives broker restarts and broker-instance transfers."
            },
            "session_uuid": {
              "type": "string",
              "format": "uuid",
              "description": "Server-owned logical session identifier. Identical across successful resumes and broker-instance transfers, and new for a fresh dialogue even when the client-selected session_id is reused. Distinct from session_id, which is client-chosen for correlation; this is not a credential."
            }
          },
          "additionalProperties": true,
          "allOf": [
            {
              "if": {
                "properties": {
                  "modality": {
                    "const": "text"
                  }
                },
                "required": [
                  "modality"
                ]
              },
              "then": {
                "not": {
                  "required": [
                    "audio"
                  ]
                }
              },
              "else": {
                "required": [
                  "audio"
                ]
              }
            }
          ]
        }
      },
      "silentMic": {
        "name": "silentMic",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "description": "Nonfatal warning that sustained digital silence or an unusually low startup signal is reaching the server. Keep the session live while the user checks the selected input, mute, and microphone permissions.",
          "required": [
            "type",
            "reason",
            "duration_ms",
            "peak"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "silent_mic"
            },
            "reason": {
              "type": "string",
              "enum": [
                "digital_silence",
                "near_silent"
              ]
            },
            "duration_ms": {
              "type": "number",
              "minimum": 0
            },
            "peak": {
              "type": "integer",
              "minimum": 0,
              "maximum": 32768,
              "description": "Highest absolute PCM16 sample observed in the detection window."
            }
          },
          "additionalProperties": true
        }
      },
      "turn": {
        "name": "turn",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "turn_id"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "turn"
            },
            "response_id": {
              "type": "string",
              "description": "Opaque logical response ID shared across bridge, tool wait and final segments."
            },
            "turn_id": {
              "type": "string",
              "description": "Compatibility segment ID; response_id identifies the full logical response."
            }
          },
          "additionalProperties": true
        }
      },
      "asr": {
        "name": "asr",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "text",
            "turn_id"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "asr"
            },
            "text": {
              "type": "string"
            },
            "turn_id": {
              "type": "string",
              "description": "Id of the turn this transcript belongs to."
            },
            "message_id": {
              "type": "string",
              "maxLength": 128,
              "description": "Client-generated correlation id for an injected text turn. Omitted for voice input."
            },
            "input_source": {
              "type": "string",
              "enum": [
                "voice",
                "text"
              ],
              "description": "Whether the final transcript came from microphone speech or injected text."
            },
            "utterance_id": {
              "type": "string"
            },
            "revision": {
              "type": "integer",
              "minimum": 1
            },
            "policy_version": {
              "type": "integer",
              "minimum": 1
            }
          },
          "additionalProperties": true
        }
      },
      "injectContextAck": {
        "name": "injectContextAck",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "message_id",
            "accepted"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "inject_context_ack"
            },
            "message_id": {
              "type": "string",
              "maxLength": 128,
              "description": "The client-generated id from inject_context."
            },
            "accepted": {
              "type": "boolean",
              "description": "Authoritative broker decision. False means the injection was not delivered."
            },
            "role": {
              "type": "string",
              "enum": [
                "user",
                "context"
              ]
            },
            "input_source": {
              "type": "string",
              "enum": [
                "text",
                "context"
              ]
            },
            "turn_id": {
              "type": "string",
              "description": "Stable server turn id for an accepted typed user turn."
            },
            "reply_started": {
              "type": "boolean",
              "description": "For context with reply=true, whether the requested reply also claimed the floor."
            },
            "retryable": {
              "type": "boolean",
              "description": "For a rejection, whether retrying later is safe."
            },
            "detail": {
              "type": "string",
              "description": "Human-readable rejection reason."
            },
            "queued": {
              "type": "boolean",
              "description": "True when accepted context requested a reply that is waiting for the caller/assistant floor. Waiting updates coalesce. The acknowledgement records the original acceptance outcome."
            }
          },
          "additionalProperties": false
        },
        "description": "Authoritative accepted/rejected acknowledgement for a correlated inject_context request."
      },
      "handoffAck": {
        "name": "handoffAck",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "operation_id",
            "status",
            "accepted"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "handoff_ack"
            },
            "operation_id": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "description": "The operation_id from continue_with_agent, tool_result.continue_with, or their deprecated handoff aliases."
            },
            "status": {
              "type": "string",
              "enum": [
                "queued",
                "applied",
                "rejected"
              ]
            },
            "accepted": {
              "type": "boolean",
              "description": "True for queued or applied; false for rejected."
            },
            "detail": {
              "type": "string",
              "description": "Human-readable rejection detail when status is rejected."
            }
          },
          "additionalProperties": false
        },
        "description": "Correlated handoff acknowledgement. Rejection leaves the active configuration unchanged."
      },
      "utterance": {
        "name": "utterance",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "text",
            "turn_id"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "utterance"
            },
            "response_id": {
              "type": "string"
            },
            "text": {
              "type": "string"
            },
            "turn_id": {
              "type": "string",
              "description": "Id of the turn this assistant utterance belongs to."
            },
            "barge_seq": {
              "type": "integer"
            },
            "corrected": {
              "type": "boolean",
              "description": "True when this frame revises the earlier interrupted utterance with the same turn_id."
            },
            "utterance_id": {
              "type": "string"
            },
            "revision": {
              "type": "integer",
              "minimum": 1
            },
            "policy_version": {
              "type": "integer",
              "minimum": 1
            }
          },
          "additionalProperties": true
        }
      },
      "done": {
        "name": "done",
        "contentType": "application/json",
        "description": "Per-segment output fence identified by turn_id. It does not settle the logical response: response_done reports logical teardown and may arrive before the final done. Neither event proves client playback.",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "turn_id"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "done"
            },
            "response_id": {
              "type": "string"
            },
            "turn_id": {
              "type": "string",
              "description": "Id of the turn that just completed."
            }
          },
          "additionalProperties": true
        }
      },
      "sessionEndRequested": {
        "name": "sessionEndRequested",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "description": "Advisory that the model requested an end after its goodbye was sent. It does not confirm that the peer heard or acted on the farewell. Stop automatic new-turn injection, but preserve output already queued locally and do not close solely for this frame. A real user turn during the server grace period cancels the request.",
          "required": [
            "type"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "session_end_requested"
            },
            "farewell": {
              "type": "string",
              "description": "The farewell argument the agent passed to end_call, present whether or not it was spoken: it is not spoken when the reply text of that turn ended a sentence. Its presence does not confirm playback or that the caller heard it. Absent when the end was not requested through the tool."
            }
          },
          "additionalProperties": true
        }
      },
      "interrupted": {
        "name": "interrupted",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "turn_id"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "interrupted"
            },
            "response_id": {
              "type": "string"
            },
            "turn_id": {
              "type": "string",
              "description": "Id of the barged turn."
            },
            "clear": {
              "type": "boolean"
            },
            "barge_seq": {
              "type": "integer"
            }
          },
          "additionalProperties": true
        }
      },
      "canceled": {
        "name": "canceled",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "turn_id"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "canceled"
            },
            "response_id": {
              "type": "string"
            },
            "turn_id": {
              "type": "string",
              "description": "Id of the eager speculative turn that was retracted."
            }
          },
          "additionalProperties": true
        }
      },
      "textDelta": {
        "name": "textDelta",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "turn_id",
            "delta"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "text_delta"
            },
            "response_id": {
              "type": "string"
            },
            "turn_id": {
              "type": "string",
              "description": "Id of the turn this text belongs to."
            },
            "delta": {
              "type": "string",
              "description": "An incremental slice of assistant text, as the model produces it. Concatenate deltas for a given turn_id to reconstruct the streamed text; the final text still arrives as utterance."
            },
            "welcome": {
              "type": "boolean",
              "description": "True when this delta belongs to the session greeting."
            }
          },
          "additionalProperties": false
        },
        "description": "Streamed assistant text, sent as the model produces it (ahead of audio and the final utterance frame)."
      },
      "permissionPending": {
        "name": "permissionPending",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "id",
            "tool",
            "arguments",
            "source",
            "status_label",
            "expires_at"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "permission_pending"
            },
            "id": {
              "type": "string",
              "description": "Session-scoped approval id. External approvers return this id through resolve_tool_permission."
            },
            "tool": {
              "type": "string",
              "description": "Name of the held tool."
            },
            "arguments": {
              "type": "object",
              "description": "Exact held arguments for review. A decision cannot change them."
            },
            "source": {
              "type": "string",
              "enum": [
                "caller",
                "application"
              ],
              "description": "Required authorization source."
            },
            "status_label": {
              "type": "string",
              "description": "Human-readable action label."
            },
            "expires_at": {
              "type": "number",
              "description": "Expiry as Unix time in seconds; a repeated request does not extend it."
            }
          },
          "additionalProperties": false
        },
        "description": "A permission-gated tool call is held for the configured caller or application approval source."
      },
      "permissionApproved": {
        "name": "permissionApproved",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "id",
            "tool"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "permission_approved"
            },
            "id": {
              "type": "string",
              "description": "The approval id that was resolved."
            },
            "tool": {
              "type": "string"
            }
          },
          "additionalProperties": false
        },
        "description": "A pending permission was approved and the admitted execution path returned, including accepted deferral. Emitted after tool_call, not as a pre-execution receipt. Handle the tool call and return its result without waiting for this event. This does not establish business success. The id matches the held tool call."
      },
      "permissionRefused": {
        "name": "permissionRefused",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "id",
            "tool"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "permission_refused"
            },
            "id": {
              "type": "string",
              "description": "The approval id that was resolved."
            },
            "tool": {
              "type": "string"
            }
          },
          "additionalProperties": false
        },
        "description": "A pending permission was declined; the held tool call did not run."
      },
      "toolCall": {
        "name": "toolCall",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "id",
            "name",
            "args"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "tool_call"
            },
            "response_id": {
              "type": "string"
            },
            "id": {
              "type": "string"
            },
            "name": {
              "type": "string"
            },
            "args": {
              "type": "object"
            }
          },
          "additionalProperties": true
        }
      },
      "toolDeferredAck": {
        "name": "toolDeferredAck",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "accepted",
            "id"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "tool_deferred_ack"
            },
            "accepted": {
              "type": "boolean"
            },
            "id": {
              "type": "string"
            },
            "handle": {
              "type": "string"
            },
            "job_id": {
              "type": "string"
            },
            "status_label": {
              "type": "string"
            },
            "deferred_timeout": {
              "type": "number"
            },
            "reason": {
              "type": "string",
              "description": "Rejection or replay reason. wait_expired means the first defer arrived after the ordinary observation deadline; existing deferred handles remain usable."
            }
          },
          "additionalProperties": true
        }
      },
      "toolDeferredResume": {
        "name": "toolDeferredResume",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "id",
            "handle",
            "name",
            "job_id",
            "status_label"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "tool_deferred_resume"
            },
            "id": {
              "type": "string"
            },
            "handle": {
              "type": "string"
            },
            "name": {
              "type": "string"
            },
            "job_id": {
              "type": "string"
            },
            "status_label": {
              "type": "string"
            }
          },
          "additionalProperties": true
        }
      },
      "toolCancelServer": {
        "name": "toolCancelServer",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "id"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "tool_cancel"
            },
            "id": {
              "type": "string"
            }
          },
          "additionalProperties": true
        }
      },
      "toolJobDeliveryFailed": {
        "name": "toolJobDeliveryFailed",
        "summary": "A queued background-job completion voiceover could not be delivered. The turn still closes with done; job_ids lists jobs whose spoken delivery is terminally failed (their results remain in conversation context and can be asked about), requeued lists jobs re-queued for one more proactive spoken attempt (a newer user turn may supersede it, as with any queued completion).",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "job_ids",
            "requeued",
            "detail"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "tool_job_delivery_failed"
            },
            "job_ids": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "Jobs whose spoken delivery is terminally failed."
            },
            "requeued": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "Jobs re-queued for one more proactive spoken attempt. A newer user turn may supersede the retry, as with any queued completion."
            },
            "detail": {
              "type": "string",
              "enum": [
                "queue_deadline_exceeded",
                "voiceover_inaudible",
                "voiceover_markup_suppressed",
                "voiceover_transport_failed"
              ],
              "description": "Stable failure code. Any other internal cause is reported as voiceover_transport_failed."
            }
          },
          "additionalProperties": true
        }
      },
      "toolJobNarration": {
        "name": "toolJobNarration",
        "summary": "Narration-lifecycle ack for a queued tool_partial_result `interaction` (docs/tool-orchestration.md, Partial results and interactions). Scoped to interaction-kind jobs only -- a plain completion has no such wire-visible lifecycle.",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "state",
            "job_ids",
            "interaction_ids",
            "kind"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "tool_job_narration"
            },
            "state": {
              "type": "string",
              "enum": [
                "queued",
                "started",
                "superseded",
                "cancelled",
                "failed",
                "resolved"
              ],
              "description": "queued: enqueued, floor busy -- always bounded: a deadline watchdog re-attempts delivery every INTERACTION_QUEUE_DEADLINE_S (10s default, up to INTERACTION_QUEUE_DEADLINE_ROUNDS rounds per delivery cycle), and an ask whose own blocked turn holds the floor yields that turn and is voiced immediately. started: now being spoken. superseded: a real user turn or barge preempted it before anything was heard (a fresh interaction can be raised again if still needed), a newer interaction on the same call replaced it, or the host closed it as superseded via tool_interaction_update. cancelled: cancel_pending_job, the host's tool_cancel of the call behind it, or a host tool_interaction_update state=cancelled dismissed it first. failed: a terminal give-up -- retried past the delivery bound, or the queue deadline expired with the floor unclaimable; telemetry-only (nothing was spoken -- fall back to your own UI for the decision). resolved: the decision no longer awaits the voice channel -- the interaction's own call sent its terminal result, or the host closed it as resolved via tool_interaction_update. A requeued ask (claimed but unheard) emits a fresh queued, so every delivery cycle ends in an observable state change."
            },
            "job_ids": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "The interaction job(s) this state applies to. Multiple pending interactions are coalesced into one turn, so more than one id may share a `started` event."
            },
            "interaction_ids": {
              "type": "array",
              "items": {
                "type": "string"
              },
              "description": "Stable interaction identities this state applies to (host-chosen `interaction.id`, or the broker-derived \"<call_id>#i<n>\"), aligned with the affected subset of job_ids. How a host learns a broker-derived id: it appears on the first queued/started ack for the ask."
            },
            "kind": {
              "type": "string",
              "description": "Always \"interaction\" today -- present so a future kind could share this ack without a new message type."
            }
          },
          "additionalProperties": false
        }
      },
      "working": {
        "name": "working",
        "summary": "Dialt is blocking on a tool result with nothing audible (the silence after the spoken bridge, until the final reply's first audio), or that wait has ended.",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "active"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "working"
            },
            "active": {
              "type": "boolean",
              "description": "True while a tool-calling turn waits for its result. False when the answer begins or the turn ends. Pure state signal; the SDK adds no background audio."
            }
          }
        }
      },
      "toolInteractionUpdateAck": {
        "name": "toolInteractionUpdateAck",
        "summary": "Deterministic answer to every toolInteractionUpdate. applied=false carries a stable reason: invalid_interaction_id, invalid_state, unknown_interaction (never seen, or from before a reconnect -- interaction state does not survive a resume), or already_closed (first close wins: cancel dismissed it, its call resolved, a newer ask replaced it, or a duplicate of this very frame -- a user turn superseding the ask's narration deliberately does NOT close the decision).",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "id",
            "interaction_id",
            "state",
            "applied",
            "reason"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "tool_interaction_update_ack"
            },
            "id": {
              "type": [
                "string",
                "null"
              ],
              "description": "The canonical server call id the update resolved to, or null if the call reference was unknown."
            },
            "interaction_id": {
              "type": [
                "string",
                "null"
              ]
            },
            "state": {
              "type": [
                "string",
                "null"
              ],
              "description": "Echo of the requested state."
            },
            "applied": {
              "type": "boolean"
            },
            "reason": {
              "type": [
                "string",
                "null"
              ],
              "enum": [
                "invalid_interaction_id",
                "invalid_state",
                "unknown_interaction",
                "already_closed",
                null
              ],
              "description": "null when applied; otherwise the stable rejection reason."
            }
          },
          "additionalProperties": false
        }
      },
      "voiceChanged": {
        "name": "voiceChanged",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "voice",
            "name"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "voice"
            },
            "voice": {
              "type": "string"
            },
            "name": {
              "type": "string"
            }
          },
          "additionalProperties": true
        }
      },
      "error": {
        "name": "error",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "detail",
            "code",
            "retryable"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "error"
            },
            "detail": {
              "type": "string",
              "description": "Human-readable detail. May keep changing freely \u2014 do not match against it; use code instead."
            },
            "code": {
              "type": "string",
              "description": "Stable machine-readable error code (e.g. unauthorized, server_busy, invalid_tools, missing_instructions, invalid_instructions). Safe to match on for retry/handling logic."
            },
            "retryable": {
              "type": "boolean",
              "description": "Whether retrying the same request is expected to succeed. Defaults per-code (e.g. server_busy/server_restarting are retryable; unauthorized/invalid_start_frame/missing_instructions are not) and is always present."
            }
          },
          "additionalProperties": true
        }
      },
      "webrtcIce": {
        "name": "webrtcIce",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "ice_servers"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "webrtc_ice"
            },
            "ice_servers": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/IceServer"
              },
              "description": "STUN plus (when configured) short-lived TURN credentials. Do not build the RTCPeerConnection until this arrives \u2014 gathering ICE earlier would miss TURN."
            },
            "trickle_ice": {
              "type": "boolean",
              "description": "When true, send webrtcOffer immediately, followed by webrtcCandidate frames and webrtcIceComplete. When absent or false, send a fully gathered offer."
            }
          },
          "additionalProperties": false
        },
        "description": "Experimental. Sent over the signaling WebSocket right after a start frame carrying transport.kind webrtc. Build the RTCPeerConnection with these ice_servers, then send webrtcOffer."
      },
      "webrtcOffer": {
        "name": "webrtcOffer",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "sdp"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "webrtc_offer"
            },
            "sdp": {
              "type": "string",
              "description": "The client peer connection's local SDP offer, created after webrtcIce's servers are configured."
            }
          },
          "additionalProperties": false
        },
        "description": "Experimental. Client's SDP offer, sent over the signaling WebSocket once the RTCPeerConnection is built with webrtcIce's servers."
      },
      "webrtcCandidate": {
        "name": "webrtcCandidate",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "candidate"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "webrtc_candidate"
            },
            "candidate": {
              "$ref": "#/components/schemas/IceCandidate"
            }
          },
          "additionalProperties": false
        },
        "description": "Experimental. One client ICE candidate sent after the offer when webrtcIce advertises trickle_ice."
      },
      "webrtcIceComplete": {
        "name": "webrtcIceComplete",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "webrtc_ice_complete"
            }
          },
          "additionalProperties": false
        },
        "description": "Experimental. Marks the end of client candidate gathering after all webrtcCandidate frames."
      },
      "webrtcAnswer": {
        "name": "webrtcAnswer",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "sdp"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "webrtc_answer"
            },
            "sdp": {
              "type": "string",
              "description": "The server peer connection's local SDP answer."
            },
            "ice_servers": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/IceServer"
              }
            }
          },
          "additionalProperties": false
        },
        "description": "Experimental. Server's SDP answer to webrtcOffer. The call then lives on the peer connection; the signaling socket may drop afterwards without affecting it."
      },
      "webrtcBye": {
        "name": "bye",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "code"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "bye"
            },
            "code": {
              "type": "integer",
              "description": "Close code from the same vocabulary as a WebSocket close code."
            },
            "reason": {
              "type": "string"
            }
          },
          "additionalProperties": false
        },
        "description": "Sent on the webrtc \"control\" data channel just before the server closes the peer connection \u2014 the webrtc transport's counterpart of a WebSocket close frame/code."
      },
      "microphoneAudio": {
        "name": "microphoneAudio",
        "contentType": "application/octet-stream",
        "payload": {
          "type": "string",
          "description": "PCM16 little-endian mono microphone audio at audio.sr, sent in binary WebSocket frames. Keep frames flowing for the whole session, silence included: the server measures turn-end silence in received audio, so a client that sends only while the user speaks leaves the turn open.",
          "format": "binary"
        }
      },
      "assistantAudio": {
        "name": "assistantAudio",
        "contentType": "application/octet-stream",
        "payload": {
          "type": "string",
          "description": "Mono assistant audio in the output_encoding and output_sr selected in ready.audio, received in binary WebSocket frames.",
          "format": "binary"
        }
      },
      "interrupt": {
        "name": "interrupt",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type"
          ],
          "properties": {
            "operation_id": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128
            },
            "response_id": {
              "type": "string",
              "minLength": 1
            },
            "target_operation_id": {
              "type": "string",
              "minLength": 1
            },
            "type": {
              "type": "string",
              "enum": [
                "interrupt",
                "cancel"
              ],
              "description": "Either spelling stops the in-flight reply the same way."
            }
          },
          "additionalProperties": false,
          "oneOf": [
            {
              "not": {
                "anyOf": [
                  {
                    "required": [
                      "operation_id"
                    ]
                  },
                  {
                    "required": [
                      "response_id"
                    ]
                  },
                  {
                    "required": [
                      "target_operation_id"
                    ]
                  }
                ]
              }
            },
            {
              "required": [
                "operation_id",
                "response_id"
              ],
              "properties": {
                "type": {
                  "const": "interrupt"
                }
              },
              "not": {
                "required": [
                  "target_operation_id"
                ]
              }
            },
            {
              "required": [
                "operation_id",
                "target_operation_id"
              ],
              "properties": {
                "type": {
                  "const": "interrupt"
                }
              },
              "not": {
                "required": [
                  "response_id"
                ]
              }
            }
          ]
        },
        "description": "Legacy uncorrelated stop remains supported. Targeted interruption requires targeted_interrupt_v1, operation_id and exactly one response_id or target_operation_id. A stale target cannot stop a newer response. WebRTC does not support targeted interruption."
      },
      "setTools": {
        "name": "setTools",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "tools"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "set_tools"
            },
            "tools": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/ToolDefinition"
              },
              "description": "Replacement tool manifest, same shape as start's mode.tools."
            }
          },
          "additionalProperties": false
        },
        "description": "Mid-session tool swap: agents that change capability by call phase no longer need a reconnect, which would destroy the conversation."
      },
      "setToolChoice": {
        "name": "setToolChoice",
        "summary": "Replace the effective tool_choice mid-session; applies from the next reply. one_shot: true reverts to the previous choice after the next caller or explicitly host-requested reply consumes the new one; autonomous jobs do not consume it. An invalid value is rejected with an `error` frame (code invalid_tool_choice) and changes nothing. A mid-session setTools resets tool_choice to \"auto\"; re-apply it after swapping tools.",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "tool_choice"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "set_tool_choice"
            },
            "tool_choice": {
              "$ref": "#/components/schemas/DialtMode/properties/tool_choice"
            },
            "one_shot": {
              "type": "boolean",
              "default": false
            }
          },
          "additionalProperties": false
        }
      },
      "triggerReply": {
        "name": "triggerReply",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "trigger_reply"
            },
            "prompt": {
              "type": "string",
              "maxLength": 2000,
              "description": "Optional nudge text for the reply. Omit to make Dialt speak first with no real user turn."
            }
          },
          "additionalProperties": false
        },
        "description": "Client-triggered reply. Silently ignored if a reply is already in flight \u2014 do not stack these."
      },
      "injectContext": {
        "name": "injectContext",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "text",
            "role"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "inject_context"
            },
            "message_id": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128,
              "description": "Client-generated correlation id echoed by inject_context_ack and, for role=user, the canonical final asr event. New SDKs always send this field."
            },
            "text": {
              "type": "string",
              "maxLength": 2000,
              "description": "The text to inject. Required, 1\u20132000 characters."
            },
            "role": {
              "type": "string",
              "enum": [
                "user",
                "context"
              ],
              "description": "\"user\": treated exactly like a real spoken user turn arriving as text instead of voice \u2014 visible in the transcript, answered the same way a turn.end is. \"context\": silent background information (a CRM lookup, a RAG snippet) appended to the model's context; never shown to the client as something the user said."
            },
            "reply": {
              "type": "boolean",
              "default": false,
              "description": "Whether this injection requests a reply. Context is appended immediately; a busy context reply is queued until idle. User-role injections can be rejected while busy. With message_id, rejection is an inject_context_ack with accepted=false; legacy uncorrelated calls receive an inject_rejected error frame."
            }
          },
          "additionalProperties": false
        },
        "description": "Text/context injection with authoritative delivery acknowledgement. Supply message_id to correlate inject_context_ack and typed-turn asr events."
      },
      "sessionClosingSoon": {
        "name": "sessionClosingSoon",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "seconds_remaining"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "session_closing_soon"
            },
            "seconds_remaining": {
              "type": "number",
              "description": "Approximate seconds until the server closes this connection: the session hit its maximum duration, a scoped API credential is expiring, or (text sessions, reason=\"idle\") the connection has been idle for configured text idle timeout with nothing in flight. Sent once, shortly before the close; any client frame cancels an idle close."
            },
            "reason": {
              "type": "string",
              "enum": [
                "idle"
              ],
              "description": "Present only for a text session's idle close. Absent for the session time limit and credential expiry."
            }
          },
          "additionalProperties": false
        },
        "description": "Warns the client shortly before the server closes the connection due to the session time limit (or scoped credential expiry) \u2014 gives the client a chance to wrap up, or to reconnect with start.resume_token once closed."
      },
      "lowCreditWarning": {
        "name": "lowCreditWarning",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "seconds_remaining"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "low_credit_warning"
            },
            "seconds_remaining": {
              "type": "number",
              "description": "Approximate remaining talk time at the current balance. Sent once, the first time it drops under the warning threshold \u2014 read-only, never adjusts the balance itself."
            }
          },
          "additionalProperties": false
        },
        "description": "API-key (paid) sessions only. Warns the client once when the account's remaining balance is running low, so the call doesn't just cut off mid-conversation when credit runs out."
      },
      "playbackReport": {
        "name": "playbackReport",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "event",
            "underruns"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "client_event"
            },
            "event": {
              "type": "string",
              "const": "playback_report"
            },
            "underruns": {
              "type": "integer",
              "minimum": 0,
              "description": "Times this reply's audio queue drained at the speaker before the next chunk arrived."
            },
            "starved_ms": {
              "type": "number",
              "minimum": 0,
              "description": "Total audible starvation across those underruns."
            },
            "max_gap_ms": {
              "type": "number",
              "minimum": 0
            },
            "event_age_ms": {
              "type": "number",
              "minimum": 0,
              "description": "Age of the browser MessageEvent when the SDK handled done. This indicates browser event-queue delay; it is not a raw network-arrival timestamp."
            },
            "turn_id": {
              "type": "string"
            }
          },
          "additionalProperties": false
        }
      },
      "playbackPause": {
        "name": "playbackPause",
        "contentType": "application/json",
        "description": "Pause and retain the unplayed PCM for this reply. Sent only to WebSocket clients advertising playback_pause_v1. Acknowledge the matching hold ID with playback_paused.",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "hold_id",
            "turn_id"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "playback_pause"
            },
            "hold_id": {
              "type": "integer",
              "minimum": 1
            },
            "turn_id": {
              "type": "string"
            }
          },
          "additionalProperties": false
        }
      },
      "playbackResume": {
        "name": "playbackResume",
        "contentType": "application/json",
        "description": "Resume the retained PCM for the matching hold ID and turn ID. Ignore stale IDs. A hard interruption or cancellation discards the held audio instead.",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "hold_id",
            "turn_id"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "playback_resume"
            },
            "hold_id": {
              "type": "integer",
              "minimum": 1
            },
            "turn_id": {
              "type": "string"
            }
          },
          "additionalProperties": false
        }
      },
      "playbackPaused": {
        "name": "playbackPaused",
        "contentType": "application/json",
        "description": "Acknowledges a provisional pause with retained duration and hardware output tail. Repeated acknowledgements may report a buffer limit error; they never authorize resuming a different hold.",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "hold_id",
            "turn_id",
            "event",
            "remaining_ms",
            "retained_ms"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "client_event"
            },
            "hold_id": {
              "type": "integer",
              "minimum": 1
            },
            "turn_id": {
              "type": "string"
            },
            "event": {
              "type": "string",
              "const": "playback_paused"
            },
            "remaining_ms": {
              "type": "number",
              "minimum": 0,
              "description": "Estimated hardware output tail after scheduled playback is stopped. The broker waits this long before resuming."
            },
            "retained_ms": {
              "type": "number",
              "minimum": 0,
              "description": "Unplayed audio retained for this hold. More than 3000 ms abandons recovery."
            },
            "error": {
              "type": "string"
            }
          },
          "additionalProperties": false
        }
      },
      "permissionExpired": {
        "name": "permissionExpired",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "id",
            "tool"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "permission_expired"
            },
            "id": {
              "type": "string",
              "description": "The approval id that was resolved."
            },
            "tool": {
              "type": "string"
            }
          },
          "additionalProperties": false
        },
        "description": "A pending request expired without execution. Reset, tool replacement and disconnect also invalidate pending ids."
      },
      "resolveToolPermission": {
        "name": "resolveToolPermission",
        "contentType": "application/json",
        "description": "Submit an application decision as the authenticated session host. Only application-source requests accept this command; the application authenticates its reviewer. Arguments and actor claims cannot be supplied.",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "id",
            "decision"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "resolve_tool_permission"
            },
            "id": {
              "type": "string",
              "minLength": 1,
              "description": "The pending application approval id, scoped to this authenticated session."
            },
            "decision": {
              "type": "string",
              "enum": [
                "approve",
                "decline"
              ]
            }
          },
          "additionalProperties": false
        }
      },
      "permissionResolution": {
        "name": "permissionResolution",
        "contentType": "application/json",
        "description": "Acknowledges an application decision. A duplicate, invalid, expired or wrong-source decision cannot execute an action. Completion uses permission_approved/refused plus normal tool-result delivery.",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "id",
            "decision",
            "accepted"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "permission_resolution"
            },
            "id": {
              "type": [
                "string",
                "null"
              ]
            },
            "decision": {
              "type": [
                "string",
                "null"
              ]
            },
            "accepted": {
              "type": "boolean",
              "description": "The decision was accepted for asynchronous resolution, not proof the tool succeeded."
            },
            "reason": {
              "type": "string",
              "enum": [
                "invalid_permission_resolution",
                "unknown_or_expired_approval_id",
                "permission_source_mismatch",
                "resolution_in_progress"
              ]
            }
          },
          "additionalProperties": false
        }
      },
      "policyFlag": {
        "name": "policyFlag",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "description": "A source-specific policy occurrence and quiet guidance delivery outcome. Updates and retractions retain occurrence identity. Delivery does not prove policy satisfaction.",
          "required": [
            "type",
            "rule",
            "action",
            "evidence",
            "delivered",
            "reply_started"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "policy_flag"
            },
            "rule": {
              "type": "string",
              "description": "The rule id from mode.policy."
            },
            "action": {
              "type": "string",
              "const": "next_turn"
            },
            "evidence": {
              "type": "string",
              "description": "The judge's one-sentence quote of what triggered the rule."
            },
            "delivered": {
              "type": "boolean",
              "description": "Guidance was appended to context; not proof that the agent followed it."
            },
            "reply_started": {
              "type": "boolean",
              "const": false
            },
            "occurrence_id": {
              "type": "string"
            },
            "utterance_id": {
              "type": "string"
            },
            "revision": {
              "type": "integer",
              "minimum": 1
            },
            "status": {
              "type": "string",
              "enum": [
                "raised",
                "updated",
                "retracted"
              ]
            }
          },
          "additionalProperties": true
        }
      },
      "policyError": {
        "name": "policyError",
        "payload": {
          "type": "object",
          "description": "Nonfatal policy monitoring failure. Does not establish clearance. No provider exception text is exposed.",
          "required": [
            "type",
            "check_id",
            "reason",
            "continues"
          ],
          "properties": {
            "type": {
              "const": "policy_error"
            },
            "check_id": {
              "oneOf": [
                {
                  "type": "integer"
                },
                {
                  "type": "string"
                }
              ]
            },
            "reason": {
              "type": "string",
              "enum": [
                "timeout",
                "provider_error",
                "checker_failed"
              ]
            },
            "continues": {
              "type": "boolean"
            }
          }
        }
      },
      "policyCheck": {
        "name": "policyCheck",
        "payload": {
          "type": "object",
          "description": "Successful tracked check. Decisions are applied, but guidance delivery may still be pending.",
          "required": [
            "type",
            "check_id",
            "policy_version",
            "status"
          ],
          "properties": {
            "type": {
              "const": "policy_check"
            },
            "check_id": {
              "type": "string"
            },
            "policy_version": {
              "type": "integer"
            },
            "status": {
              "const": "completed"
            }
          }
        }
      },
      "policySettled": {
        "name": "policySettled",
        "payload": {
          "type": "object",
          "description": "Tracked policy judging and guidance attempts have settled through this version. Does not assert that every guidance delivery succeeded or the agent obeyed it.",
          "required": [
            "type",
            "policy_version",
            "checked_version",
            "healthy"
          ],
          "properties": {
            "type": {
              "const": "policy_settled"
            },
            "policy_version": {
              "type": "integer"
            },
            "checked_version": {
              "type": "integer"
            },
            "healthy": {
              "type": "boolean"
            }
          }
        }
      },
      "wrapUp": {
        "name": "wrapUp",
        "summary": "Request a graceful host-initiated sign-off in voice or text mode, including when mode.end_call is disabled. An active reply receives a bounded grace before the sign-off. No dedicated acknowledgement is sent; continue handling reply events and normal closure, and drain local playback. Sending this frame does not confirm the farewell was heard.",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "wrap_up"
            },
            "reason": {
              "type": "string",
              "default": "time_limit",
              "description": "Reason included in the wrap-up context. Omitted or empty values use time_limit; strings are truncated to 40 characters."
            }
          },
          "additionalProperties": false
        }
      },
      "inputAudioDone": {
        "name": "inputAudioDone",
        "summary": "Commit accumulated voice input when start.mode.turn_detection is client. Send after the audio for that turn. Ignored when client turn detection is not active; not a text-mode control and not an acknowledgement of transcription or reply completion.",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "event"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "client_event"
            },
            "event": {
              "type": "string",
              "const": "input_audio_done"
            }
          },
          "additionalProperties": false
        }
      },
      "asrCorrection": {
        "name": "asrCorrection",
        "summary": "Replaces the text of an earlier voice transcript. When turn_id is present, update that turn rather than appending a new user turn. turn_id can be omitted; do not fabricate a correlation. Optional utterance_id, revision and policy_version describe the policy transcript revision.",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "text"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "asr_correction"
            },
            "text": {
              "type": "string"
            },
            "turn_id": {
              "type": "string",
              "description": "Id of the turn this transcript belongs to."
            },
            "utterance_id": {
              "type": "string"
            },
            "revision": {
              "type": "integer",
              "minimum": 1
            },
            "policy_version": {
              "type": "integer",
              "minimum": 1
            }
          },
          "additionalProperties": true
        }
      },
      "auth": {
        "name": "auth",
        "summary": "Check a credential on a fresh WebSocket connection. Send as the first frame instead of start, with api_key or a Bearer handshake header. Returns ok on success or error on failure and the connection ends. Does not open a conversation, emit ready, or prove credit or session capacity is available.",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "auth"
            },
            "api_key": {
              "type": "string",
              "description": "Persistent or scoped key. Optional when supplied in the WebSocket Authorization header."
            }
          },
          "additionalProperties": false
        }
      },
      "feedback": {
        "name": "feedback",
        "summary": "Submit feedback as the first frame on a fresh authenticated WebSocket instead of start. Requires an owned recording and either rating or nonblank text. Success returns ok and the connection ends. Re-submitting feedback for the same session replaces the earlier feedback. Does not consume a conversation slot and remains available while the server drains.",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "session_id"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "feedback"
            },
            "api_key": {
              "type": "string",
              "description": "Persistent or scoped key. Optional when supplied in the WebSocket Authorization header."
            },
            "session_id": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-][A-Za-z0-9._-]{0,63}$",
              "minLength": 1,
              "maxLength": 64,
              "description": "Client session_id of a recording owned by the authenticated account; not the server session_uuid."
            },
            "rating": {
              "type": "string",
              "enum": [
                "up",
                "down"
              ]
            },
            "text": {
              "type": "string",
              "pattern": "\\S",
              "description": "Feedback comment; trimmed and truncated to 2000 characters. Required if rating is omitted."
            },
            "device": {
              "type": "string",
              "enum": [
                "mac",
                "iphone",
                "ipad",
                "windows",
                "android",
                "linux",
                "other"
              ]
            },
            "browser": {
              "type": "string",
              "enum": [
                "chrome",
                "safari",
                "firefox",
                "edge",
                "other"
              ]
            }
          },
          "additionalProperties": false,
          "anyOf": [
            {
              "required": [
                "rating"
              ]
            },
            {
              "required": [
                "text"
              ]
            }
          ]
        }
      },
      "clientError": {
        "name": "clientError",
        "summary": "Report a client-side failure as the first frame on a fresh authenticated WebSocket instead of start. Omit session_id when failure predates session creation; when present it must name an owned recording. Success returns ok and the connection ends, without consuming a conversation slot. detail and context are diagnostic log content: do not send transcripts, replies, credentials or user conversation text.",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type",
            "detail"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "client_error"
            },
            "api_key": {
              "type": "string",
              "description": "Persistent or scoped key. Optional when supplied in the WebSocket Authorization header."
            },
            "session_id": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-][A-Za-z0-9._-]{0,63}$",
              "minLength": 1,
              "maxLength": 64,
              "description": "Client session_id of a recording owned by the authenticated account; not the server session_uuid."
            },
            "detail": {
              "type": "string",
              "pattern": "\\S",
              "description": "Nonblank failure diagnostic; trimmed and truncated to 500 characters."
            },
            "context": {
              "type": "string",
              "description": "Optional diagnostic context; trimmed and truncated to 200 characters."
            }
          },
          "additionalProperties": false
        }
      },
      "ok": {
        "name": "ok",
        "summary": "Successful acknowledgement for a fresh-connection auth, feedback or client_error request. The connection ends after this response; it is not the ready event for a conversation.",
        "contentType": "application/json",
        "payload": {
          "type": "object",
          "required": [
            "type"
          ],
          "properties": {
            "type": {
              "type": "string",
              "const": "ok"
            }
          },
          "additionalProperties": false
        }
      }
    },
    "schemas": {
      "SpeechActivity": {
        "type": "object",
        "required": [
          "speech_id",
          "timestamp_ms"
        ],
        "properties": {
          "speech_id": {
            "type": "string",
            "minLength": 1
          },
          "timestamp_ms": {
            "type": "number",
            "minimum": 0
          },
          "reason": {
            "type": "string"
          }
        }
      },
      "ImportedConversationItem": {
        "oneOf": [
          {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "item_id",
              "revision",
              "kind",
              "role",
              "text"
            ],
            "properties": {
              "item_id": {
                "type": "string",
                "minLength": 1
              },
              "revision": {
                "type": "integer",
                "minimum": 1
              },
              "kind": {
                "const": "message"
              },
              "role": {
                "enum": [
                  "user",
                  "assistant"
                ]
              },
              "text": {
                "type": "string",
                "minLength": 1
              },
              "interrupted": {
                "type": "boolean"
              }
            },
            "if": {
              "required": [
                "interrupted"
              ]
            },
            "then": {
              "properties": {
                "role": {
                  "const": "assistant"
                }
              }
            }
          },
          {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "item_id",
              "revision",
              "kind",
              "call_id",
              "name",
              "arguments"
            ],
            "properties": {
              "item_id": {
                "type": "string",
                "minLength": 1
              },
              "revision": {
                "type": "integer",
                "minimum": 1
              },
              "kind": {
                "const": "tool_call"
              },
              "call_id": {
                "type": "string",
                "minLength": 1
              },
              "name": {
                "type": "string",
                "minLength": 1
              },
              "arguments": {
                "type": "object"
              }
            }
          },
          {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "item_id",
              "revision",
              "kind",
              "call_id",
              "content",
              "outcome"
            ],
            "properties": {
              "item_id": {
                "type": "string",
                "minLength": 1
              },
              "revision": {
                "type": "integer",
                "minimum": 1
              },
              "kind": {
                "const": "tool_result"
              },
              "call_id": {
                "type": "string",
                "minLength": 1
              },
              "content": {},
              "outcome": {
                "enum": [
                  "succeeded",
                  "failed",
                  "cancelled",
                  "timed_out",
                  "unknown"
                ]
              }
            }
          }
        ]
      },
      "ConversationItem": {
        "type": "object",
        "required": [
          "item_id",
          "revision",
          "kind"
        ],
        "description": "Accepted public item. Same fields as imported items; server-owned message revisions may have empty text. Evidence over 64 KiB uses content_omitted=true. No internal prompts or reasoning.",
        "properties": {
          "item_id": {
            "type": "string",
            "minLength": 1
          },
          "revision": {
            "type": "integer",
            "minimum": 1
          },
          "kind": {
            "enum": [
              "message",
              "tool_call",
              "tool_result"
            ]
          },
          "role": {
            "enum": [
              "user",
              "assistant"
            ]
          },
          "text": {
            "type": "string"
          },
          "interrupted": {
            "type": "boolean"
          },
          "call_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "arguments": {
            "type": [
              "object",
              "null"
            ],
            "description": "Null only when oversized live evidence sets content_omitted=true. Imported arguments always remain JSON objects."
          },
          "content": {},
          "outcome": {
            "enum": [
              "succeeded",
              "failed",
              "cancelled",
              "timed_out",
              "unknown"
            ]
          },
          "content_omitted": {
            "const": true
          }
        },
        "additionalProperties": false,
        "allOf": [
          {
            "if": {
              "properties": {
                "kind": {
                  "const": "message"
                }
              }
            },
            "then": {
              "required": [
                "role",
                "text"
              ],
              "not": {
                "anyOf": [
                  {
                    "required": [
                      "call_id"
                    ]
                  },
                  {
                    "required": [
                      "arguments"
                    ]
                  },
                  {
                    "required": [
                      "content"
                    ]
                  },
                  {
                    "required": [
                      "outcome"
                    ]
                  },
                  {
                    "required": [
                      "name"
                    ]
                  }
                ]
              }
            }
          },
          {
            "if": {
              "properties": {
                "kind": {
                  "const": "tool_call"
                }
              }
            },
            "then": {
              "required": [
                "call_id",
                "name",
                "arguments"
              ],
              "not": {
                "anyOf": [
                  {
                    "required": [
                      "role"
                    ]
                  },
                  {
                    "required": [
                      "text"
                    ]
                  },
                  {
                    "required": [
                      "content"
                    ]
                  },
                  {
                    "required": [
                      "outcome"
                    ]
                  },
                  {
                    "required": [
                      "interrupted"
                    ]
                  }
                ]
              }
            }
          },
          {
            "if": {
              "properties": {
                "kind": {
                  "const": "tool_result"
                }
              }
            },
            "then": {
              "required": [
                "call_id",
                "content",
                "outcome"
              ],
              "not": {
                "anyOf": [
                  {
                    "required": [
                      "role"
                    ]
                  },
                  {
                    "required": [
                      "text"
                    ]
                  },
                  {
                    "required": [
                      "arguments"
                    ]
                  },
                  {
                    "required": [
                      "name"
                    ]
                  },
                  {
                    "required": [
                      "interrupted"
                    ]
                  }
                ]
              }
            }
          },
          {
            "if": {
              "required": [
                "arguments"
              ],
              "properties": {
                "arguments": {
                  "type": "null"
                }
              }
            },
            "then": {
              "required": [
                "content_omitted"
              ]
            }
          },
          {
            "if": {
              "required": [
                "interrupted"
              ]
            },
            "then": {
              "properties": {
                "role": {
                  "const": "assistant"
                }
              }
            }
          }
        ]
      },
      "Voice": {
        "type": "object",
        "required": [
          "key",
          "name",
          "kind",
          "descriptor"
        ],
        "properties": {
          "key": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "regional",
              "designed"
            ]
          },
          "descriptor": {
            "type": "string",
            "description": "Display context such as additional regional context; may be empty."
          }
        }
      },
      "AudioConfig": {
        "type": "object",
        "required": [
          "sr"
        ],
        "properties": {
          "sr": {
            "type": "integer",
            "minimum": 8000,
            "maximum": 192000
          },
          "uplink_format": {
            "type": "string"
          },
          "output_encoding": {
            "type": "string",
            "enum": [
              "pcm16",
              "pcm_f32le"
            ],
            "default": "pcm16",
            "description": "Downlink (assistant audio) sample encoding \u2014 a separate concept from the uplink sr above. pcm16 is the wire default; pcm_f32le is opt-in for clients (e.g. the LiveKit plugin) that want float samples."
          },
          "output_sr": {
            "type": "integer",
            "enum": [
              8000,
              16000,
              24000
            ],
            "default": 16000,
            "description": "Downlink sample rate for assistantAudio frames. Supported values are 8000 for telephony, 16000 (default), and 24000 for wider-band playback. This is independent of the fixed 16000 Hz microphone uplink."
          }
        },
        "additionalProperties": false
      },
      "ClientInfo": {
        "type": "object",
        "properties": {
          "user": {
            "type": "string"
          },
          "timezone": {
            "type": "string"
          },
          "capabilities": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Supported optional protocol features. playback_pause_v1 opts WebSocket audio playback into provisional pause/resume with retained PCM and matching hold acknowledgements. WebRTC and phone relays do not advertise it."
          },
          "audio_frontend": {
            "type": "string",
            "description": "Diagnostic label for the active microphone echo-cancellation frontend, or unknown before SDK-owned capture starts."
          }
        },
        "additionalProperties": false
      },
      "Mode": {
        "$ref": "#/components/schemas/DialtMode"
      },
      "TransportConfig": {
        "type": "object",
        "required": [
          "kind"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "const": "webrtc",
            "description": "Experimental. Selects the WebRTC media transport (UDP, SRTP/Opus) instead of the default WebSocket binary frames. No sdp here - send it once webrtcIce arrives with TURN credentials. Requires mode.kind dialt."
          }
        },
        "additionalProperties": false,
        "description": "Omit start.transport entirely to use the default WebSocket media transport."
      },
      "IceServer": {
        "type": "object",
        "required": [
          "urls"
        ],
        "properties": {
          "urls": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "username": {
            "type": "string"
          },
          "credential": {
            "type": "string"
          }
        },
        "additionalProperties": false
      },
      "IceCandidate": {
        "type": "object",
        "required": [
          "candidate"
        ],
        "properties": {
          "candidate": {
            "type": "string",
            "minLength": 1
          },
          "sdpMid": {
            "type": [
              "string",
              "null"
            ]
          },
          "sdpMLineIndex": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0
          },
          "usernameFragment": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "anyOf": [
          {
            "required": [
              "sdpMid"
            ],
            "properties": {
              "sdpMid": {
                "type": "string"
              }
            }
          },
          {
            "required": [
              "sdpMLineIndex"
            ],
            "properties": {
              "sdpMLineIndex": {
                "type": "integer",
                "minimum": 0
              }
            }
          }
        ],
        "additionalProperties": false
      },
      "DialtMode": {
        "type": "object",
        "required": [
          "kind",
          "instructions"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "const": "dialt"
          },
          "modality": {
            "type": "string",
            "enum": [
              "voice",
              "text"
            ],
            "default": "voice",
            "description": "The I/O surface for this session. text uses input_text and emits no audio; voice uses microphone and assistant audio. The model, instructions, tools, history, and conversation events are otherwise the same."
          },
          "brain": {
            "type": "string",
            "enum": [
              "fast",
              "smart"
            ],
            "default": "fast",
            "description": "Dialt conversational tier: fast (default) or smart."
          },
          "voice": {
            "type": "string",
            "minLength": 1,
            "description": "Optional stock voice key from GET /v1/voices. Explicit selection takes precedence over a saved preference, then US Female (us_female) is the shared default, independently of Dialt Fast or Dialt Smart. A resumed session retains its confirmed stock voice identity unless explicitly changed. Empty, unknown, retired or unavailable keys fail with nonretryable invalid_voice; there is no fallback. Accepted aliases: southern_us_female maps to us_female (Southern US to general US English accent); brazilian_male maps to brazilian_female (male to female). Migration: https://dialt.com/docs/api/websocket/#voice-catalogue"
          },
          "multilingual": {
            "type": "boolean",
            "default": false,
            "description": "Experimental multilingual conversation option for Dialt voice and text sessions, applying to every available voice. Fixed at session start and retained across reconnect/resume; changes require a new session. Language quality varies by voice and is not guaranteed for arbitrary languages. Prerecorded tool bridges are disabled; model-authored speech before tool calls is preserved."
          },
          "instructions": {
            "type": "string",
            "minLength": 1,
            "pattern": "\\S",
            "description": "Required. The agent's role: who it is, whose side it is on, its goals, what it knows, the policy it follows and its stance toward the other party. Dialt's platform prompt (speech output, conversation mechanics, capabilities, the voice's register and call facts) carries no role. A fresh start without non-empty instructions fails with nonretryable missing_instructions and closes with 1008; a start with resume_token inherits the session's instructions. Guidance: https://dialt.com/docs/api/websocket/#instructions"
          },
          "tools": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ToolDefinition"
            }
          },
          "web_search": {
            "type": "boolean",
            "description": "Enables the explicit managed web_search tool. False does not disable search inside other platform-managed reasoning tools when those capabilities are enabled."
          },
          "end_call": {
            "default": false,
            "description": "Declares the managed end_call(farewell) tool so the agent can end the session. true uses Dialt's default ending condition (the caller asks you to end the call); {\"when\": \"<condition>\"} states the host's own, declared to the agent as part of the tool (1-400 chars). On that turn the server sends one goodbye. Reply text in that turn that ends a sentence is the goodbye, and the farewell is not sent after it; reply text that stops mid-sentence is followed by the farewell, which completes it; with no reply text the farewell is the goodbye. Text streamed before the call follows the same rule, and the farewell is sent if none of that text was heard. The server then sends advisory session_end_requested with the farewell argument. The server waits a short grace in which a real user turn cancels the request, then otherwise closes with code 1000. Neither done nor session_end_requested confirms that the peer heard or acted on the farewell; a client owns local playback drain after normal close. Off: the agent cannot end the session; the host ends it with wrap_up or by closing. The tool name end_call is reserved while enabled, like web_search (invalid_tools).",
            "oneOf": [
              {
                "type": "boolean"
              },
              {
                "type": "object",
                "required": [
                  "when"
                ],
                "additionalProperties": false,
                "properties": {
                  "when": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 400
                  }
                }
              }
            ]
          },
          "greeting": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "boolean",
                "const": false
              }
            ],
            "description": "Optional pre-authored opening assistant turn. In voice sessions it uses ordinary reply playback: callers may barge it, buffered audio is cleared, and only the heard prefix is retained in conversation context. A completed opener is retained in full. In text sessions it is emitted and retained as a completed assistant turn. False disables the opener."
          },
          "silence_nudge_s": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Per-session override of the server's silence check-in delay (env default 10s). Must be less than silence_end_s; omit to keep the server default. An invalid value (non-positive, or not less than silence_end_s) falls back to the server default rather than rejecting the session."
          },
          "silence_end_s": {
            "type": "number",
            "exclusiveMinimum": 0,
            "description": "Per-session override of the server's silence sign-off + session-end delay (env default 20s), counted after the check-in. Must be greater than silence_nudge_s; omit to keep the server default. An invalid value falls back to the server default rather than rejecting the session."
          },
          "tool_choice": {
            "oneOf": [
              {
                "type": "string",
                "enum": [
                  "auto",
                  "none",
                  "required"
                ]
              },
              {
                "type": "object",
                "required": [
                  "allowed"
                ],
                "properties": {
                  "allowed": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "string"
                    }
                  }
                },
                "additionalProperties": false
              },
              {
                "type": "object",
                "required": [
                  "tool"
                ],
                "properties": {
                  "tool": {
                    "type": "string"
                  }
                },
                "additionalProperties": false
              }
            ],
            "description": "Controls when Dialt may call your tools. Requires a non-empty `tools` list; `allowed`/`tool` names must be CLIENT-declared tools (broker-managed protocol tools are not valid targets; unknown names fail the session with invalid_tool_choice). The durable (non-one-shot) choice survives a resume via the session stash. `required`/`allowed`/`tool` constrain the FIRST planning round of each caller turn; later rounds of the same turn are unconstrained so the spoken reply can still be composed. A one-shot also applies to an explicitly host-requested reply, while autonomous jobs do not consume it. `none` withholds the client-declared tools while broker-managed protocol tools (job cancellation, permission resolution, managed search) stay available. Speculative drafts are planned under the active choice and dropped when it changes."
          },
          "policy": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "rules"
            ],
            "description": "Define conditions and actions once. Include them in agent instructions and/or enable background guidance. Both settings default true. Background guidance is quiet, references its source caller or assistant turn, and does not execute tools or request a reply.",
            "properties": {
              "subject": {
                "type": "string",
                "minLength": 1,
                "maxLength": 200
              },
              "include_instructions": {
                "type": "boolean",
                "default": true
              },
              "background_guidance": {
                "type": "boolean",
                "default": true
              },
              "rules": {
                "type": "array",
                "minItems": 1,
                "maxItems": 12,
                "items": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "id",
                    "when",
                    "do"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "pattern": "^[a-z][a-z0-9_]{0,39}$"
                    },
                    "when": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 1000
                    },
                    "do": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 1000
                    }
                  }
                }
              }
            }
          },
          "turn_detection": {
            "type": "string",
            "enum": [
              "server",
              "client"
            ],
            "default": "server",
            "description": "Voice input boundaries: server detects turns automatically; client waits for client_event with event input_audio_done after each audio turn. Has no effect in text modality, where input_text commits a turn."
          }
        },
        "additionalProperties": false
      },
      "ToolDefinition": {
        "type": "object",
        "allOf": [
          {
            "if": {
              "properties": {
                "deferred": {
                  "const": false
                }
              }
            },
            "then": {
              "not": {
                "anyOf": [
                  {
                    "required": [
                      "deferred_timeout"
                    ]
                  },
                  {
                    "required": [
                      "notify_on_complete"
                    ]
                  }
                ]
              }
            }
          },
          {
            "if": {
              "required": [
                "permission_source"
              ]
            },
            "then": {
              "required": [
                "requires_permission"
              ],
              "properties": {
                "requires_permission": {
                  "const": true
                }
              }
            }
          },
          {
            "not": {
              "required": [
                "recorded_bridge",
                "expected_duration"
              ]
            }
          }
        ],
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "description": "Client tool name. Platform-managed names such as end_call and web_search are reserved when those capabilities are enabled."
          },
          "description": {
            "type": "string",
            "default": "",
            "description": "State when to call, prerequisites, effects and what each result confirms. Distinguish requesting an action from completing it. Put tool-specific rules here, not only in session instructions."
          },
          "parameters": {
            "type": "object",
            "description": "JSON Schema for arguments. Describe field meaning, units and allowed values; mark required fields explicitly. State whether partial input is accepted and when to submit it."
          },
          "read_only": {
            "type": "boolean",
            "default": false,
            "description": "Allows early speculative execution. Use only for harmless, cheap lookups."
          },
          "requires_permission": {
            "type": "boolean",
            "default": false,
            "description": "Hold the exact call until its configured approval source authorizes it."
          },
          "resolver_only": {
            "type": "boolean",
            "default": false,
            "description": "This tool only receives the caller's answer to one of the host's own questions (an interaction resolver). The model never sees it and cannot call it; Dialt constructs the exact call when the caller answers. Cannot combine with requires_permission or read_only."
          },
          "recorded_bridge": {
            "type": "string",
            "enum": [
              "auto",
              "always",
              "never"
            ],
            "default": "auto",
            "description": "Policy for Dialt's prerecorded latency cover only. auto races the complete answer against the tier response budget and plays cover if the budget wins; always plays cover when the tool route commits; never waits silently for the result or timeout. Model-authored text before a call remains conversation speech. Cannot be combined with expected_duration."
          },
          "expected_duration": {
            "type": "string",
            "enum": [
              "instant",
              "seconds",
              "long"
            ],
            "deprecated": true,
            "description": "Deprecated compatibility alias for recorded_bridge. instant maps to auto; seconds and long map to always. Cannot be combined with recorded_bridge."
          },
          "status_label": {
            "type": "string",
            "minLength": 1,
            "description": "Short user-safe label Dialt may use when reporting pending work. Defaults to the tool name."
          },
          "timeout": {
            "type": "number",
            "minimum": 1,
            "maximum": 600,
            "default": 30,
            "description": "Observation deadline. Expiry releases waiting with an unknown outcome but does not cancel accepted work; a later terminal result can still resolve the call."
          },
          "deferred": {
            "type": "boolean",
            "default": false,
            "description": "Allow the host to detach a call from its current voice turn."
          },
          "deferred_timeout": {
            "type": "number",
            "minimum": 1,
            "maximum": 86400,
            "default": 7200,
            "description": "Observation window after an accepted tool_deferred frame. Valid only when deferred is true and at least timeout. Expiry does not cancel work; the existing call/handle remains correlated for a late result."
          },
          "notify_on_complete": {
            "type": "boolean",
            "default": true,
            "description": "Proactively wake the brain when background work completes; the result always enters context. Valid only when deferred is true. Also governs an application-approved call that returns its result immediately rather than deferring."
          },
          "permission_source": {
            "type": "string",
            "enum": [
              "caller",
              "application"
            ],
            "default": "caller",
            "description": "Requires requires_permission=true. caller uses caller consent and independent AI verification; application waits for resolve_tool_permission from the authenticated session host. Caller consent cannot satisfy application approval."
          }
        },
        "additionalProperties": false
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "Dialt API key",
        "description": "Persistent or session-scoped Dialt key in the WebSocket Authorization header. Browser clients that cannot set handshake headers may instead send api_key in the start frame."
      }
    }
  }
}
