{
  "components": {
    "schemas": {
      "AudioConfig": {
        "additionalProperties": false,
        "description": "The agent's audio pipeline, configured once \u2014 not per run.",
        "properties": {
          "noise_cancellation": {
            "$ref": "#/components/schemas/NoiseCancellation",
            "default": "off",
            "description": "Which filter cleans the user's inbound audio \u2014 see\n:class:`NoiseCancellation`. ``denoise`` for a shared\nmicrophone, ``voice_focus`` for one speaker. ``true`` and ``false`` are\naccepted as the pre-mode spelling of ``voice_focus`` and ``off``."
          },
          "output": {
            "default": true,
            "description": "Whether the agent emits audio. ``False`` leaves the session silent \u2014\nfor transcription, captioning, or text-response apps \u2014 while input\ntranscription and text output are unaffected. Part of the persona: a\ntext-only agent never speaks.\n\nHow that silence is achieved depends on the provider, and it matters for\nbilling. A provider with a native text-only mode stops generating speech;\none without a modalities knob keeps generating audio and has its output\ngated instead, so audio output tokens still accrue. Rejected at session\nstart when the resolved model owns its audio path end to end and cannot\nbe gated either way.",
            "type": "boolean"
          }
        },
        "title": "AudioConfig",
        "type": "object"
      },
      "BotLlmStartedEvent": {
        "description": "Model began generating its turn (first text or audio event arrived).\n\nDistinct from ``bot-started-speaking`` so UI can show \"thinking\u2026\" before\naudio is on the wire \u2014 useful when the model takes a moment to start\ngenerating, or when tool calls precede speech.",
        "properties": {
          "type": {
            "const": "bot-llm-started",
            "default": "bot-llm-started",
            "description": "The event type. Always ``bot-llm-started``.",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "title": "BotLlmStartedEvent",
        "type": "object"
      },
      "BotLlmStoppedEvent": {
        "description": "Model finished generating its turn.\n\nThe counterpart to ``bot-llm-started``: it ends the \"thinking\u2026\"\nindicator. Generation ending is not the turn ending \u2014 ``turn-complete``\nmarks that. Where this falls relative to the turn's final ``transcript``\ndepends on the upstream pipeline, so drive turn state off\n``turn-complete`` rather than off the order these two arrive in.",
        "properties": {
          "type": {
            "const": "bot-llm-stopped",
            "default": "bot-llm-stopped",
            "description": "The event type. Always ``bot-llm-stopped``.",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "title": "BotLlmStoppedEvent",
        "type": "object"
      },
      "BotStartedSpeakingEvent": {
        "description": "First audio frame of an assistant turn left the server.\n\nInformation-only \u2014 UI uses this for \"speaking\u2026\" indicators / waveform\nactivation. Clients must NOT gate the mic on this event.",
        "properties": {
          "type": {
            "const": "bot-started-speaking",
            "default": "bot-started-speaking",
            "description": "The event type. Always ``bot-started-speaking``.",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "title": "BotStartedSpeakingEvent",
        "type": "object"
      },
      "BotStoppedSpeakingEvent": {
        "description": "Last audio frame of an assistant turn left the server.\n\nInformation-only, the counterpart to ``bot-started-speaking``: it ends\nthe \"speaking\u2026\" indicator. Playback on the client may still be running,\nso this is not the moment the user stops hearing audio.",
        "properties": {
          "type": {
            "const": "bot-stopped-speaking",
            "default": "bot-stopped-speaking",
            "description": "The event type. Always ``bot-stopped-speaking``.",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "title": "BotStoppedSpeakingEvent",
        "type": "object"
      },
      "BotTtsStartedEvent": {
        "description": "Assistant speech synthesis started.\n\nPublished immediately before ``bot-started-speaking``, off the same\nstate change \u2014 treat the two as one moment rather than expecting a\nmeasurable gap between them.",
        "properties": {
          "type": {
            "const": "bot-tts-started",
            "default": "bot-tts-started",
            "description": "The event type. Always ``bot-tts-started``.",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "title": "BotTtsStartedEvent",
        "type": "object"
      },
      "BotTtsStoppedEvent": {
        "description": "Assistant speech synthesis finished.\n\nThe counterpart to ``bot-tts-started``, published immediately before\n``bot-stopped-speaking`` off the same state change.",
        "properties": {
          "type": {
            "const": "bot-tts-stopped",
            "default": "bot-tts-stopped",
            "description": "The event type. Always ``bot-tts-stopped``.",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "title": "BotTtsStoppedEvent",
        "type": "object"
      },
      "CatalogAgentConfig": {
        "additionalProperties": false,
        "description": "Run a workspace catalog agent by handle \u2014 the stored config runs\nverbatim. Only per-run ride-alongs may accompany the launch:\n``inputs`` (per-run input values), ``tools`` (client-executed\ndeclarations the server cannot provide), and ``voice`` (per-run\nspeaking style plus the one cosmetic override). Other stored-config\nfields (``instructions``, ``model``, \u2026) are structurally absent from\nthis variant \u2014 sending one is a schema error (``extra=\"forbid\"``), not\na runtime rejection.",
        "properties": {
          "inputs": {
            "additionalProperties": {
              "type": "string"
            },
            "description": "Per-run values for the referenced agent's declared input fields\n(e.g. ``{\"caller_name\": \"Sam\"}``), substituted into the resolved\nprompt's ``{{key}}`` placeholders. ``None`` / empty injects nothing.",
            "type": "object"
          },
          "name": {
            "description": "Workspace catalog agent to run, by its workspace-unique machine\nhandle (a hyphen-only slug). The server resolves it at session start\nand fails closed \u2014 an unknown or cross-workspace name rejects before\nany provider allocation.",
            "maxLength": 100,
            "pattern": "^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$",
            "type": "string"
          },
          "tools": {
            "description": "Tool set for the session: client-executed specs the SDK fulfils\nlocally, opt-in server tools by typed kind, and inline server-tool\ndefinitions (one wire kind per executor type). Used verbatim \u2014 the\nstored agent config carries no tools, so nothing is merged in.\n``None`` / empty \u2192 the session runs with no tools.",
            "items": {
              "discriminator": {
                "mapping": {
                  "catalog": "#/components/schemas/CatalogToolSpec",
                  "client": "#/components/schemas/ClientToolSpec",
                  "detect_objects": "#/components/schemas/DetectObjectsToolSpec",
                  "end_call": "#/components/schemas/EndCallToolSpec",
                  "examine_image": "#/components/schemas/ExamineImageToolSpec",
                  "point_at_object": "#/components/schemas/PointAtObjectToolSpec",
                  "screen_locate": "#/components/schemas/ScreenLocateToolSpec",
                  "server": "#/components/schemas/ServerToolSpec",
                  "speaker_log": "#/components/schemas/SpeakerLogToolSpec",
                  "web_search": "#/components/schemas/WebSearchToolSpec"
                },
                "propertyName": "kind"
              },
              "oneOf": [
                {
                  "$ref": "#/components/schemas/ClientToolSpec"
                },
                {
                  "$ref": "#/components/schemas/ServerToolSpec"
                },
                {
                  "$ref": "#/components/schemas/CatalogToolSpec"
                },
                {
                  "$ref": "#/components/schemas/WebSearchToolSpec"
                },
                {
                  "$ref": "#/components/schemas/ExamineImageToolSpec"
                },
                {
                  "$ref": "#/components/schemas/DetectObjectsToolSpec"
                },
                {
                  "$ref": "#/components/schemas/PointAtObjectToolSpec"
                },
                {
                  "$ref": "#/components/schemas/ScreenLocateToolSpec"
                },
                {
                  "$ref": "#/components/schemas/EndCallToolSpec"
                },
                {
                  "$ref": "#/components/schemas/SpeakerLogToolSpec"
                }
              ]
            },
            "maxItems": 10000,
            "type": "array"
          },
          "type": {
            "const": "catalog",
            "default": "catalog",
            "description": "Selects the agent form. Always ``catalog`` \u2014 a workspace catalog agent by name.",
            "type": "string"
          },
          "voice": {
            "$ref": "#/components/schemas/VoiceConfig",
            "description": "Per-run voice for the referenced agent: ``speaking_style`` is\nclient-resolved per-run text, and ``name`` is the one cosmetic exception\nto \"stored config runs verbatim\" \u2014 it changes how the agent sounds,\nnever what it says or can do. ``None`` keeps the stored voice."
          }
        },
        "required": [
          "name",
          "type"
        ],
        "title": "CatalogAgentConfig",
        "type": "object"
      },
      "CatalogToolSpec": {
        "additionalProperties": false,
        "description": "Attach a workspace-defined tool by its catalog name\n(``voice_agent_tools.name``); the server resolves the stored definition.\n\nProtocol reservation only: the wire shape is fixed, but neither\nsession-start flow executes catalog tools yet \u2014 a config carrying one is\nrejected with a typed 422 (``invalid_tool_config``). Unknown fields are\na schema error.",
        "properties": {
          "kind": {
            "const": "catalog",
            "default": "catalog",
            "description": "The tool kind. Always ``catalog``.",
            "type": "string"
          },
          "name": {
            "description": "Catalog name of the workspace-defined tool to attach. Reserved: no\nsession-start flow executes catalog tools yet, so a config carrying one\nis refused.",
            "maxLength": 100,
            "type": "string"
          }
        },
        "required": [
          "name",
          "kind"
        ],
        "title": "CatalogToolSpec",
        "type": "object"
      },
      "ClientActivityEnd": {
        "additionalProperties": false,
        "description": "Client signals end-of-turn for manual-VAD turn-taking. Distinct from\n``ClientEnd`` (whole-session teardown).",
        "properties": {
          "type": {
            "const": "activity-end",
            "default": "activity-end",
            "description": "The message type. Always ``activity-end``.",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "title": "ClientActivityEnd",
        "type": "object"
      },
      "ClientBindInput": {
        "additionalProperties": false,
        "description": "Bind the agent's audio input to this client.\n\nSent after the client publishes its own audio (the human voice) so the\nagent listens to *this* participant. The server binds to the sender's\nparticipant identity \u2014 a client can only bind its own input \u2014 and the pin is\nsticky thereafter. A client that joins only to receive events or serve\nclient tools, publishing no audio, never sends this and so is never the\nvoice.",
        "properties": {
          "type": {
            "const": "bind-input",
            "default": "bind-input",
            "description": "The message type. Always ``bind-input``.",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "title": "ClientBindInput",
        "type": "object"
      },
      "ClientConnectTimings": {
        "additionalProperties": false,
        "description": "How long the client's own half of the connect took, sent once the\nsession is live.\n\nThe server measures its own phases and the client measures its own; only\nthe client can see the API call, the media join and the microphone\npermission, so without this report the connect waterfall stops at the\nworker's edge. Durations only, in milliseconds: absolute instants would\nhave to be trusted against a clock the server has no way to check.\n\n``server`` echoes back the timings the session-start response carried,\nbecause the worker that records the session never saw that response.\n\nA session accepts one of these, so an empty or ill-formed report must\nfail here rather than downstream: the frame is dropped at parse and the\nslot stays open for a later usable one. The same requirement is published\nin the schema, so a client cannot build a message the contract calls valid\nand the worker then drops.",
        "minProperties": 2,
        "properties": {
          "mic_ms": {
            "description": "Local capture: microphone requested \u2192 publishing.",
            "minimum": 0.0,
            "type": "integer"
          },
          "ready_ms": {
            "description": "The whole client-side wait: session start requested \u2192 agent ready.",
            "minimum": 0.0,
            "type": "integer"
          },
          "request_ms": {
            "description": "Session-start API call: request sent \u2192 response received.",
            "minimum": 0.0,
            "type": "integer"
          },
          "room_ms": {
            "description": "Media transport: room connect \u2192 connected.",
            "minimum": 0.0,
            "type": "integer"
          },
          "server": {
            "$ref": "#/components/schemas/SessionStartTimings",
            "description": "The server-side breakdown from the session-start response, echoed back\nso both halves of the connect land on one record."
          },
          "type": {
            "const": "connect-timings",
            "default": "connect-timings",
            "description": "The message type. Always ``connect-timings``.",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "title": "ClientConnectTimings",
        "type": "object"
      },
      "ClientContext": {
        "additionalProperties": false,
        "description": "Add text to the model's context without asking it to reply.\n\nThe content rides the provider's pre-turn channel, so the model is never\nasked for a response and cannot open a turn for it; it reads the note as\nbackground when it next answers the user. This is the opposite of\n``send-text``, which *is* a turn.\n\nFor live application state (scroll position, selection, current record,\nform values) that should inform the agent without interrupting it.",
        "properties": {
          "content": {
            "description": "The note to put in front of the model. Read as background, never\nanswered directly.",
            "maxLength": 4096,
            "minLength": 1,
            "type": "string"
          },
          "type": {
            "const": "send-context",
            "default": "send-context",
            "description": "The message type. Always ``send-context``.",
            "type": "string"
          }
        },
        "required": [
          "content",
          "type"
        ],
        "title": "ClientContext",
        "type": "object"
      },
      "ClientEnd": {
        "additionalProperties": false,
        "description": "User ended the session. Server tears down the upstream session and closes.",
        "properties": {
          "type": {
            "const": "end",
            "default": "end",
            "description": "The message type. Always ``end``.",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "title": "ClientEnd",
        "type": "object"
      },
      "ClientEnvelope": {
        "additionalProperties": false,
        "description": "Generic chunked carrier for any oversized client message.\n\nThe SDK auto-wraps any outbound message whose JSON exceeds the\ntransport's per-packet threshold into a sequence of envelope chunks; the\nserver buffers by ``envelope_id``, concatenates the base64-encoded UTF-8\nbytes back into the original JSON, and re-runs classification.\n\n``data`` is a base64-encoded fragment of the UTF-8 bytes of the inner\nmessage JSON. Base64 keeps every chunk ASCII-safe \u2014 splitting at byte\nboundaries can land mid-codepoint in a raw UTF-8 substring, which would\ncorrupt the decode on the server.\n\nOrdering is guaranteed by the reliable channel (chunks arrive in ``seq``\norder); ``envelope_id`` keeps concurrent envelopes from interleaving.\n``seq`` runs ``0..total-1`` and the assembler emits the reassembled\ninner message as soon as the last chunk arrives.",
        "properties": {
          "data": {
            "description": "This chunk's slice of the inner message, base64-encoded.",
            "type": "string"
          },
          "envelope_id": {
            "description": "Groups the chunks of one message, so concurrent envelopes do not\ninterleave.",
            "type": "string"
          },
          "seq": {
            "description": "Position of this chunk, ``0`` to ``total - 1``.",
            "type": "integer"
          },
          "total": {
            "description": "How many chunks the message was split into.",
            "type": "integer"
          },
          "type": {
            "const": "envelope-chunk",
            "default": "envelope-chunk",
            "description": "The message type. Always ``envelope-chunk``.",
            "type": "string"
          }
        },
        "required": [
          "envelope_id",
          "seq",
          "total",
          "data",
          "type"
        ],
        "title": "ClientEnvelope",
        "type": "object"
      },
      "ClientImage": {
        "additionalProperties": false,
        "description": "One image frame from the client \u2014 screen share, camera capture, or\nany other visual input the application wants to feed to the model.\n\nCarried as base64 in JSON so it rides the same control channel as text\nand tool calls (the audio channel stays pure PCM). Oversized frames are\nwrapped in ``ClientEnvelope`` chunks transparently by the SDK.\n\n``stream_id`` lets the application label multiple concurrent video\nstreams (e.g. ``camera``, ``screen_share_main``).",
        "properties": {
          "data": {
            "description": "The frame itself, base64-encoded.",
            "type": "string"
          },
          "mime_type": {
            "description": "Media type of the encoded frame, e.g. ``image/jpeg``.",
            "type": "string"
          },
          "stream_id": {
            "description": "Labels this stream so several concurrent video streams stay\ndistinguishable. Always sent; it has no server-side default.",
            "type": "string"
          },
          "type": {
            "const": "send-image",
            "default": "send-image",
            "description": "The message type. Always ``send-image``.",
            "type": "string"
          }
        },
        "required": [
          "mime_type",
          "data",
          "stream_id",
          "type"
        ],
        "title": "ClientImage",
        "type": "object"
      },
      "ClientMute": {
        "additionalProperties": false,
        "description": "Toggle the mic gate. While muted the client drops outbound audio frames.",
        "properties": {
          "muted": {
            "description": "``true`` gates outbound audio, ``false`` reopens it.",
            "type": "boolean"
          },
          "type": {
            "const": "mute",
            "default": "mute",
            "description": "The message type. Always ``mute``.",
            "type": "string"
          }
        },
        "required": [
          "muted",
          "type"
        ],
        "title": "ClientMute",
        "type": "object"
      },
      "ClientPing": {
        "additionalProperties": false,
        "description": "Heartbeat. Server replies with ``PongEvent``.",
        "properties": {
          "type": {
            "const": "ping",
            "default": "ping",
            "description": "The message type. Always ``ping``.",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "title": "ClientPing",
        "type": "object"
      },
      "ClientText": {
        "additionalProperties": false,
        "description": "Send a text message instead of audio.",
        "properties": {
          "content": {
            "description": "The message text, treated exactly as a spoken turn would be.",
            "type": "string"
          },
          "type": {
            "const": "send-text",
            "default": "send-text",
            "description": "The message type. Always ``send-text``.",
            "type": "string"
          }
        },
        "required": [
          "content",
          "type"
        ],
        "title": "ClientText",
        "type": "object"
      },
      "ClientToolSpec": {
        "additionalProperties": false,
        "description": "One client-executed tool, self-described at session start.\n\nThe server materializes a session-scoped tool definition from each spec,\nso a new client tool ships with a client release alone \u2014 no per-tool\nbackend code. Field names mirror MCP's ``Tool`` descriptor.\n\nOnly size/resource bounds are enforced here (they bind at the API edge);\ncontent checks soft-reject per spec server-side and are echoed on\n``ReadyEvent.rejected_tools`` \u2014 a client can see *why* a spec was\ndropped instead of debugging silence.",
        "properties": {
          "description": {
            "description": "What the tool does, written for the model \u2014 this is what it decides\nfrom when choosing to call it.",
            "maxLength": 2048,
            "type": "string"
          },
          "kind": {
            "const": "client",
            "default": "client",
            "description": "The tool kind. Always ``client``.",
            "type": "string"
          },
          "name": {
            "description": "Name the model calls the tool by, and the name the SDK dispatches on\nlocally. Unique within the session's tool set.",
            "maxLength": 64,
            "type": "string"
          },
          "parameters": {
            "additionalProperties": true,
            "description": "JSON Schema for the tool's arguments \u2014 restricted dialect (``type`` /\n``properties`` / ``required`` / ``items`` / ``enum`` / ``description``),\ntop-level ``type: \"object\"``.",
            "type": "object"
          }
        },
        "required": [
          "name",
          "description",
          "parameters",
          "kind"
        ],
        "title": "ClientToolSpec",
        "type": "object"
      },
      "CosmoVadConfig": {
        "additionalProperties": false,
        "description": "Tuning for the ``cosmo_vad`` turn detector. Every knob names the\ndetector's own machinery, so a caller always knows which endpointer a\nsetting touches; an unset knob keeps the server default.",
        "properties": {
          "max_hold_ms": {
            "description": "Total silence, in milliseconds, after which the turn ends regardless\nof the classifier's verdict \u2014 the bound on how long a pause the\nclassifier reads as mid-thought can hold the turn open.",
            "maximum": 5000.0,
            "minimum": 0.0,
            "type": "integer"
          },
          "pause_ms": {
            "description": "Silence, in milliseconds, that triggers the end-of-turn inference.",
            "maximum": 5000.0,
            "minimum": 0.0,
            "type": "integer"
          },
          "prefix_ms": {
            "description": "Audio, in milliseconds, kept from before speech was detected, so a\nturn's opening syllable is not clipped.",
            "maximum": 5000.0,
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "title": "CosmoVadConfig",
        "type": "object"
      },
      "CredentialKind": {
        "description": "Which of the two realtime credentials the server saw.",
        "enum": [
          "api_key",
          "user_token"
        ],
        "title": "CredentialKind",
        "type": "string"
      },
      "DelegationAppend": {
        "additionalProperties": false,
        "description": "Hand text back to the voice model for work it delegated to you, or\nsteer it outside any delegation.\n\nThree channels: ``thinking`` is background the model keeps to itself,\n``commentary`` is something it says now in its own words, and\n``instructions`` changes how it behaves from here on. Each append is\none short piece; send several as work progresses rather than one\nlong one at the end.",
        "properties": {
          "channel": {
            "$ref": "#/components/schemas/DelegationChannel",
            "description": "How the text reaches the model."
          },
          "content": {
            "description": "The text. Under ``commentary`` the model paraphrases it rather than\nreading it verbatim.",
            "maxLength": 4096,
            "minLength": 1,
            "type": "string"
          },
          "delegation_id": {
            "description": "The ``delegation-created`` event this answers. ``None`` steers the\nsession as a whole, outside any delegation.",
            "type": "string"
          },
          "type": {
            "const": "delegation-append",
            "default": "delegation-append",
            "description": "The message type. Always ``delegation-append``.",
            "type": "string"
          }
        },
        "required": [
          "channel",
          "content",
          "type"
        ],
        "title": "DelegationAppend",
        "type": "object"
      },
      "DelegationChannel": {
        "description": "How an appended text reaches the voice model.",
        "enum": [
          "thinking",
          "commentary",
          "instructions"
        ],
        "title": "DelegationChannel",
        "type": "string"
      },
      "DelegationCreatedEvent": {
        "description": "The voice model decided the user's request needs work done and\nhanded it to your application. Do the work, then answer with\n``delegation-append`` messages carrying this event's id; the model\nkeeps talking with the user meanwhile.",
        "properties": {
          "delegation_id": {
            "description": "Identifies this hand-off. Pass it on every ``delegation-append``\nthat answers it.",
            "type": "string"
          },
          "transcript": {
            "description": "What the user said in the turn that prompted the hand-off. Earlier\nturns are yours to keep from the ``transcript`` events.",
            "type": "string"
          },
          "type": {
            "const": "delegation-created",
            "default": "delegation-created",
            "description": "The event type. Always ``delegation-created``.",
            "type": "string"
          }
        },
        "required": [
          "delegation_id",
          "transcript",
          "type"
        ],
        "title": "DelegationCreatedEvent",
        "type": "object"
      },
      "DetectObjectsToolSpec": {
        "additionalProperties": false,
        "description": "Opt-in to the server-executed object locator, which returns boxes.\n\nLocates a named object in the freshest camera/screen frame \u2014 one box\nper matching instance \u2014 and hands the candidates to the model, which\npicks one and passes it to whichever renderer the client declared.\nZero-config; unknown fields are a schema error. Availability is checked\nat session start: a deployment that cannot run the tool starts the\nsession without it and reports the drop on ``ready.rejected_tools``.\nA session with no frame gets the tool's own typed answer at call time.",
        "properties": {
          "kind": {
            "const": "detect_objects",
            "default": "detect_objects",
            "description": "The tool kind. Always ``detect_objects``.",
            "type": "string"
          }
        },
        "required": [
          "kind"
        ],
        "title": "DetectObjectsToolSpec",
        "type": "object"
      },
      "DialBody": {
        "additionalProperties": false,
        "description": "Request body for placing an outbound dial on a running session.",
        "properties": {
          "caller_number": {
            "description": "E.164 caller-ID to present, e.g. +14155550100. Must be an ACTIVE number in the workspace's pool (rejected with 400 caller_number_not_available otherwise). Omit to use the trunk default.",
            "maxLength": 20,
            "type": "string"
          },
          "phone_number": {
            "description": "E.164 destination to call, e.g. +14155550199.",
            "maxLength": 20,
            "type": "string"
          }
        },
        "required": [
          "phone_number"
        ],
        "title": "DialBody",
        "type": "object"
      },
      "DialResponse": {
        "description": "Result of ``POST /session/{id}/dial`` \u2014 the dial was queued. The\ncall rings asynchronously; ``dial_id`` is the handle to correlate it.\nLive status / outcome is observed via session events, not this response.",
        "properties": {
          "dial_id": {
            "description": "Identifies the queued dial, for your own logs. There is no status\nendpoint to pass it to \u2014 the call's progress is observed through session\nevents.",
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "dial_id"
        ],
        "title": "DialResponse",
        "type": "object"
      },
      "EndCall": {
        "additionalProperties": false,
        "description": "End-call action for a server hook.",
        "properties": {
          "farewell": {
            "description": "Parting line to speak before hanging up. ``None`` ends the call\nwithout one.",
            "maxLength": 4096,
            "type": "string"
          },
          "type": {
            "const": "end_call",
            "default": "end_call",
            "description": "The action type. Always ``end_call``.",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "title": "EndCall",
        "type": "object"
      },
      "EndCallToolSpec": {
        "additionalProperties": false,
        "description": "Opt-in to the server-executed hang-up tool, so the model can end the\ncall itself once the exchange is finished.\n\nThe server owns the model-facing declaration \u2014 zero-config; unknown\nfields are a schema error. No availability gate: declaring it is the\ngrant. Ending binds the call, not just the agent \u2014 every leg drops \u2014\nand the spoken goodbye is allowed to finish playing first.\n\nNot the ``end_call`` silence-hook action, which hangs up on a caller who\nstopped talking; this is the hang-up the model decides on.",
        "properties": {
          "kind": {
            "const": "end_call",
            "default": "end_call",
            "description": "The tool kind. Always ``end_call``.",
            "type": "string"
          }
        },
        "required": [
          "kind"
        ],
        "title": "EndCallToolSpec",
        "type": "object"
      },
      "EndOfSpeechSensitivity": {
        "description": "How readily the Gemini realtime model decides the user's turn ended.\n\nMirrors ``google.genai.types.EndSensitivity`` (minus the unspecified\nsentinel), the end-of-turn counterpart to the start-of-speech sensitivity\n``interruption_sensitivity`` drives. ``high`` ends the turn sooner (lower\nendpointing latency, more likely to cut in on a mid-thought pause);\n``None`` keeps the provider default.",
        "enum": [
          "low",
          "high"
        ],
        "title": "EndOfSpeechSensitivity",
        "type": "string"
      },
      "ErrorBody": {
        "description": "The error payload non-2xx external API responses carry.\n\n``type`` is the error family (``api_error`` / ``validation_error`` /\n``internal_error``). ``code`` is the machine-readable rejection code for\ntyped session-start rejections (e.g. ``version_mismatch``,\n``invalid_tool_config``); absent otherwise. ``errors`` lists field-level\nviolations for ``validation_error`` responses.\n\nOne exception: requests rejected by the authentication layer (401)\nreturn ``{\"detail\": \"...\"}`` without this envelope.",
        "properties": {
          "code": {
            "description": "Machine-readable rejection code for a typed rejection (e.g.\n``version_mismatch``); absent on untyped errors. Match on this rather\nthan on ``message``.",
            "type": "string"
          },
          "errors": {
            "description": "Field-level violations, present on ``validation_error`` responses.",
            "items": {
              "$ref": "#/components/schemas/FieldError"
            },
            "type": "array"
          },
          "message": {
            "description": "Human-readable explanation. Written for a person to read, not part of\nthe contract.",
            "type": "string"
          },
          "type": {
            "description": "Error family: ``api_error``, ``validation_error`` or\n``internal_error``.",
            "type": "string"
          }
        },
        "required": [
          "type",
          "message"
        ],
        "title": "ErrorBody",
        "type": "object"
      },
      "ErrorCode": {
        "description": "Stable error codes carried on wire-protocol error events; clients\nswitch on them to choose recovery behavior. Distinct from the REST\nrejection codes carried on error envelopes (``error.code``).",
        "enum": [
          "auth_failed",
          "workspace_forbidden",
          "voice_disabled",
          "upstream_disconnect",
          "internal_error",
          "invalid_message",
          "version_mismatch"
        ],
        "title": "ErrorCode",
        "type": "string"
      },
      "ErrorEnvelope": {
        "description": "Wire shape of non-2xx external API responses, except auth-layer 401s\n(which return ``{\"detail\": \"...\"}``).",
        "properties": {
          "error": {
            "$ref": "#/components/schemas/ErrorBody",
            "description": "The error payload."
          }
        },
        "required": [
          "error"
        ],
        "title": "ErrorEnvelope",
        "type": "object"
      },
      "ErrorEvent": {
        "description": "Recoverable or terminal error. Clients switch on ``code`` for\nrecovery UX.\n\n``fatal=True`` signals the session is dead; the client should tear down\nand reconnect rather than retry the current turn. ``fatal=False`` means\nthis turn failed but the session can continue.",
        "properties": {
          "code": {
            "$ref": "#/components/schemas/ErrorCode",
            "description": "Stable code to switch on for recovery. Match this, not\n``message``."
          },
          "fatal": {
            "default": false,
            "description": "``true`` means the session is dead and must be torn down; ``false``\nmeans only this turn failed.",
            "type": "boolean"
          },
          "message": {
            "description": "Human-readable explanation, for logs and display.",
            "type": "string"
          },
          "type": {
            "const": "error",
            "default": "error",
            "description": "The event type. Always ``error``.",
            "type": "string"
          }
        },
        "required": [
          "code",
          "message",
          "type"
        ],
        "title": "ErrorEvent",
        "type": "object"
      },
      "ExamineImageToolSpec": {
        "additionalProperties": false,
        "description": "Opt-in to the server-executed frame-examination tool.\n\nReads the freshest frame of the client's published video (camera or\nscreen share) at full resolution to answer a fine-detail question.\nThe server owns the model-facing declaration \u2014 zero-config; unknown\nfields are a schema error. Sessions without a fresh frame get the\ntool's own typed \"no frame\" answer at call time; there is no\nsession-start availability gate.",
        "properties": {
          "kind": {
            "const": "examine_image",
            "default": "examine_image",
            "description": "The tool kind. Always ``examine_image``.",
            "type": "string"
          }
        },
        "required": [
          "kind"
        ],
        "title": "ExamineImageToolSpec",
        "type": "object"
      },
      "ExperimentalParams": {
        "additionalProperties": false,
        "description": "Unstable session-config knobs.\n\nFields here are experimental: unlike the rest of the protocol they may\nchange shape or disappear between releases. Stable equivalents graduate\nto top-level ``SessionConfig`` fields.",
        "properties": {
          "avatar": {
            "description": "When set, a vendor renderer joins the room and republishes the\nagent's speech as lip-synced video; the agent then publishes no audio\nof its own. Refused, and the session starts without one, unless the\nworkspace has the avatar flag on.",
            "discriminator": {
              "mapping": {
                "tavus": "#/components/schemas/TavusAvatar"
              },
              "propertyName": "provider"
            },
            "oneOf": [
              {
                "$ref": "#/components/schemas/TavusAvatar"
              }
            ]
          },
          "resume_session_id": {
            "description": "When set, the server resumes the named prior session \u2014 natively when\na resumption handle is still warm, otherwise by seeding the new\nupstream session with the prior transcript. The server picks between\nthe two; clients just pass the id.",
            "format": "uuid",
            "type": "string"
          }
        },
        "title": "ExperimentalParams",
        "type": "object"
      },
      "FieldError": {
        "description": "One field-level violation from request-schema validation: a path into\nthe caller's own request plus the standard validation error type and\nmessage. Payload values are never echoed back.",
        "properties": {
          "loc": {
            "description": "Path to the offending field in the request body, outermost first.",
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array"
          },
          "msg": {
            "description": "Human-readable description of the violation.",
            "type": "string"
          },
          "type": {
            "description": "Validation error type, e.g. ``missing`` or ``string_too_long``.",
            "type": "string"
          }
        },
        "required": [
          "loc"
        ],
        "title": "FieldError",
        "type": "object"
      },
      "GeminiModel": {
        "additionalProperties": false,
        "description": "The Gemini-realtime provider with its knobs. Assigning this block to\n``model`` picks the provider; the ``provider`` discriminator makes setting\na Gemini knob for any other provider a schema error rather than a silent\nno-op.\n\n``turn_detection`` selects which detector ends the user's turn, and each\ndetector owns its knobs: ``end_of_speech_sensitivity``,\n``silence_duration_ms`` and ``prefix_padding_ms`` tune the provider's\n``server_vad``; the ``cosmo_vad`` block tunes ``cosmo_vad`` (the Gemini\ndefault). Naming a detector and sending the other one's knobs is rejected\nat session start rather than silently ignored.",
        "properties": {
          "cosmo_vad": {
            "$ref": "#/components/schemas/CosmoVadConfig",
            "description": "Tuning for the ``cosmo_vad`` detector. Valid only while that detector\nruns (``turn_detection`` unset or ``cosmo_vad``); sending it alongside\n``server_vad`` is rejected. ``None`` keeps the server defaults."
          },
          "end_of_speech_sensitivity": {
            "$ref": "#/components/schemas/EndOfSpeechSensitivity",
            "description": "How readily the model decides the user's turn ended \u2014 the end-of-turn\ncounterpart to ``interruption_sensitivity``'s speech-start gate. ``high``\nendpoints sooner, so the assistant answers faster but is more likely to\ncut in on a mid-thought pause. Read only with ``server_vad``. ``None``\nkeeps the provider default."
          },
          "include_thoughts": {
            "description": "Whether the model streams thought summaries alongside its answer. Only\nworth enabling for an app that reads them. ``None`` keeps the server's\nper-mode default.",
            "type": "boolean"
          },
          "max_output_tokens": {
            "description": "Cap on tokens per model response. ``None`` uses the provider default.",
            "maximum": 32768.0,
            "minimum": 1.0,
            "type": "integer"
          },
          "model_id": {
            "description": "Concrete Gemini model to run. ``None`` runs the provider default. A\nmodel id that is not a Gemini model is rejected at session start.",
            "type": "string"
          },
          "prefix_padding_ms": {
            "description": "Audio, in milliseconds, kept from before speech was detected, so a\nturn's opening syllable is not clipped. Read only with ``server_vad``.\n``None`` keeps the provider default.",
            "maximum": 5000.0,
            "minimum": 0.0,
            "type": "integer"
          },
          "provider": {
            "const": "gemini",
            "description": "Names the provider this block configures. Always ``gemini``;\nthe SDKs stamp it, so you never write it yourself.",
            "type": "string"
          },
          "silence_duration_ms": {
            "description": "Silence, in milliseconds, that ends the user's turn. Lower shortens the\nwait before the model starts answering; too low fragments a turn across a\nnatural pause. Read only with ``server_vad``. ``None`` keeps the provider\ndefault.",
            "maximum": 5000.0,
            "minimum": 0.0,
            "type": "integer"
          },
          "temperature": {
            "description": "Sampling temperature \u2014 higher is more varied, lower more deterministic.\n``None`` uses the provider default.",
            "maximum": 2.0,
            "minimum": 0.0,
            "type": "number"
          },
          "thinking_level": {
            "$ref": "#/components/schemas/ThinkingLevel",
            "description": "Reasoning depth. ``None`` keeps the server's per-mode default."
          },
          "tool_response_overrides": {
            "additionalProperties": {
              "$ref": "#/components/schemas/GeminiToolResponsePolicy"
            },
            "description": "Response policies keyed by declared tool name, replacing the default for those tools.\nUse blocking overrides for actions whose result must precede further speech.",
            "type": "object"
          },
          "tool_response_policy": {
            "$ref": "#/components/schemas/GeminiToolResponsePolicy",
            "description": "Default tool behavior. Omitted keeps tools blocking, except Extended Thinking,\nwhich requires non-blocking tools and does not accept scheduling."
          },
          "turn_detection": {
            "$ref": "#/components/schemas/TurnDetectionMode",
            "description": "Which end-of-turn detector runs. ``None`` (the default) and\n``cosmo_vad`` run Cosmo's semantic turn detection, which classifies\nwhether the utterance reads as finished instead of timing a silence\nwindow \u2014 a mid-thought pause no longer ends the turn. ``server_vad``\nopts the session into the provider's own silence-window detection,\nwhich is what the three knobs below tune; they are unread under the\ndefault detector. ``semantic_vad`` is OpenAI-only and rejected."
          }
        },
        "required": [
          "provider"
        ],
        "title": "GeminiModel",
        "type": "object"
      },
      "GeminiToolResponsePolicy": {
        "additionalProperties": false,
        "description": "Whether Gemini waits for a tool and when it responds to its result.",
        "properties": {
          "behavior": {
            "description": "``blocking`` waits for the result; ``non_blocking`` allows speech while it runs.",
            "enum": [
              "blocking",
              "non_blocking"
            ],
            "type": "string"
          },
          "scheduling": {
            "description": "For non-blocking tools: answer when idle, absorb silently, or interrupt speech.\nOmitted uses ``when_idle`` on Gemini Live and model scheduling on Extended Thinking.",
            "enum": [
              "when_idle",
              "silent",
              "interrupt"
            ],
            "type": "string"
          }
        },
        "required": [
          "behavior"
        ],
        "title": "GeminiToolResponsePolicy",
        "type": "object"
      },
      "GrokModel": {
        "additionalProperties": false,
        "description": "The xAI Grok Voice provider with its knobs. Grok pins its own sampling\nand token limits; turn-taking, reasoning effort, and playback speed are\ntunable here.\n\nGrok runs one detector \u2014 a fixed silence window \u2014 so the turn-taking\nknobs below always apply. Naming any other detector is rejected at\nsession start rather than silently downgraded.",
        "properties": {
          "idle_timeout_ms": {
            "description": "Milliseconds of user silence after a response before the server\nre-engages the user, re-arming after every response. ``None`` never\nre-engages.",
            "maximum": 120000.0,
            "minimum": 0.0,
            "type": "integer"
          },
          "model_id": {
            "description": "Concrete Grok Voice model to run. ``None`` runs the provider default.",
            "type": "string"
          },
          "prefix_padding_ms": {
            "description": "Audio, in milliseconds, kept from before speech was detected. ``None``\nkeeps the server's default.",
            "maximum": 5000.0,
            "minimum": 0.0,
            "type": "integer"
          },
          "provider": {
            "const": "grok",
            "description": "Names the provider this block configures. Always ``grok``;\nthe SDKs stamp it, so you never write it yourself.",
            "type": "string"
          },
          "reasoning_effort": {
            "$ref": "#/components/schemas/GrokReasoningEffort",
            "description": "Whether the model reasons before speaking. Grok's own default is\n``high``, which buys benchmark-grade answers at multi-second turn\nlatency; ``none`` answers immediately. ``None`` keeps Grok's default."
          },
          "silence_duration_ms": {
            "description": "Silence, in milliseconds, that ends the user's turn. ``None`` keeps the\nserver's default.",
            "maximum": 5000.0,
            "minimum": 0.0,
            "type": "integer"
          },
          "speed": {
            "description": "Playback-rate multiplier for the agent's speech (0.7\u20131.5). ``None``\nkeeps normal speed (1.0).",
            "maximum": 1.5,
            "minimum": 0.7,
            "type": "number"
          },
          "turn_detection": {
            "$ref": "#/components/schemas/TurnDetectionMode",
            "description": "Which turn detector runs. ``server_vad`` is the only one Grok offers,\nand ``None`` selects it. ``semantic_vad`` and ``cosmo_vad`` are rejected \u2014\nthe first is an OpenAI detector Grok has no equivalent of, the second is\nCosmo's own and runs only on Gemini."
          }
        },
        "required": [
          "provider"
        ],
        "title": "GrokModel",
        "type": "object"
      },
      "GrokReasoningEffort": {
        "description": "Whether the Grok Voice model reasons before speaking.\n\nGrok's own default is ``HIGH``, which buys deliberate answers at\nmulti-second turn latency; ``NONE`` answers immediately.",
        "enum": [
          "high",
          "none"
        ],
        "title": "GrokReasoningEffort",
        "type": "string"
      },
      "ImportedTranscriptTurn": {
        "additionalProperties": false,
        "description": "A transcript line on the way in. Same shape as the response model,\nstrict about unknown keys because it is a request body \u2014 a mistyped\nfield is a caller error, not something to silently drop.",
        "properties": {
          "role": {
            "description": "Who spoke it.",
            "enum": [
              "user",
              "assistant"
            ],
            "type": "string"
          },
          "text": {
            "description": "What was said.",
            "type": "string"
          },
          "ts": {
            "description": "When the line was spoken, epoch seconds.",
            "type": "number"
          }
        },
        "required": [
          "ts",
          "role",
          "text"
        ],
        "title": "ImportedTranscriptTurn",
        "type": "object"
      },
      "InlineAgentConfig": {
        "additionalProperties": false,
        "description": "Define the agent inline \u2014 the persona/configuration of the model on\nthe other end, independent of any one run. Reused unchanged across\nsessions. Catalog-only fields (``name``, ``inputs``) are structurally\nabsent \u2014 sending one is a schema error (``extra=\"forbid\"``), so a\nmistagged catalog launch fails loudly instead of silently running the\nneutral default agent.",
        "properties": {
          "audio": {
            "$ref": "#/components/schemas/AudioConfig",
            "description": "The agent's audio pipeline \u2014 output emission and inbound noise\ncancellation. ``None`` keeps every default (audio on, no\ncancellation)."
          },
          "greeting": {
            "description": "Opening line the assistant speaks first, voiced as soon as the model\nsession opens \u2014 before the client even receives ``ready``. Part of the\npersona: what this agent says to open a call. ``None`` (the default)\nkeeps the wait-for-user behavior; a resumed session never re-greets.",
            "maxLength": 4000,
            "type": "string"
          },
          "hooks": {
            "description": "Server hooks: declarative trigger\u2192action configs the server\nexecutes. On the wire these are the only hooks that exist \u2014 an SDK's\nclient-side callback hooks never serialize.",
            "items": {
              "$ref": "#/components/schemas/SilenceTimeout"
            },
            "maxItems": 16,
            "type": "array"
          },
          "instructions": {
            "description": "Caller-supplied system instructions. Replaces the server's neutral\ndefault when set; ``None`` keeps the default. Capped at 131072\ncharacters, checked at session start; the cap is fixed rather than\nderived from the selected model's context budget.",
            "type": "string"
          },
          "interruption_sensitivity": {
            "$ref": "#/components/schemas/InterruptionSensitivity",
            "default": "default",
            "description": "How readily user audio barges in over the assistant, and (where the\nprovider supports it) how long the assistant waits before\ntreating the user's turn as complete. ``low`` raises the provider's\nspeech-start gate and lengthens the end-of-turn wait, so ambient noise is\nless likely to cut the assistant off mid-utterance and a pause or\nbackchannel is less likely to end the user's turn early \u2014 at the cost of\na slower response. ``high`` lowers both."
          },
          "model": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "discriminator": {
                  "mapping": {
                    "gemini": "#/components/schemas/GeminiModel",
                    "grok": "#/components/schemas/GrokModel",
                    "openai": "#/components/schemas/OpenAIModel",
                    "openai_live": "#/components/schemas/OpenAILiveModel",
                    "openai_mini": "#/components/schemas/OpenAIMiniModel"
                  },
                  "propertyName": "provider"
                },
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/GeminiModel"
                  },
                  {
                    "$ref": "#/components/schemas/OpenAIModel"
                  },
                  {
                    "$ref": "#/components/schemas/OpenAIMiniModel"
                  },
                  {
                    "$ref": "#/components/schemas/OpenAILiveModel"
                  },
                  {
                    "$ref": "#/components/schemas/GrokModel"
                  }
                ]
              }
            ],
            "description": "What runs on the other end: a family alias or concrete model id\n(string form), or a provider block carrying that provider's knobs and an\noptional concrete ``model_id``. ``None`` lets the server choose its\ndefault. The valid set is provider/workspace-dependent: the server\nvalidates the value at session start and rejects unavailable ones\nexplicitly. Part of the persona: the same agent runs the same model the\nsame way across sessions."
          },
          "tools": {
            "description": "Tool set for the session: client-executed specs the SDK fulfils\nlocally, opt-in server tools by typed kind, and inline server-tool\ndefinitions (one wire kind per executor type). ``None`` / empty \u2192 the\nsession runs with no tools.",
            "items": {
              "discriminator": {
                "mapping": {
                  "catalog": "#/components/schemas/CatalogToolSpec",
                  "client": "#/components/schemas/ClientToolSpec",
                  "detect_objects": "#/components/schemas/DetectObjectsToolSpec",
                  "end_call": "#/components/schemas/EndCallToolSpec",
                  "examine_image": "#/components/schemas/ExamineImageToolSpec",
                  "point_at_object": "#/components/schemas/PointAtObjectToolSpec",
                  "screen_locate": "#/components/schemas/ScreenLocateToolSpec",
                  "server": "#/components/schemas/ServerToolSpec",
                  "speaker_log": "#/components/schemas/SpeakerLogToolSpec",
                  "web_search": "#/components/schemas/WebSearchToolSpec"
                },
                "propertyName": "kind"
              },
              "oneOf": [
                {
                  "$ref": "#/components/schemas/ClientToolSpec"
                },
                {
                  "$ref": "#/components/schemas/ServerToolSpec"
                },
                {
                  "$ref": "#/components/schemas/CatalogToolSpec"
                },
                {
                  "$ref": "#/components/schemas/WebSearchToolSpec"
                },
                {
                  "$ref": "#/components/schemas/ExamineImageToolSpec"
                },
                {
                  "$ref": "#/components/schemas/DetectObjectsToolSpec"
                },
                {
                  "$ref": "#/components/schemas/PointAtObjectToolSpec"
                },
                {
                  "$ref": "#/components/schemas/ScreenLocateToolSpec"
                },
                {
                  "$ref": "#/components/schemas/EndCallToolSpec"
                },
                {
                  "$ref": "#/components/schemas/SpeakerLogToolSpec"
                }
              ]
            },
            "maxItems": 10000,
            "type": "array"
          },
          "type": {
            "const": "inline",
            "default": "inline",
            "description": "Selects the agent form. Always ``inline`` \u2014 an agent defined inline.",
            "type": "string"
          },
          "voice": {
            "$ref": "#/components/schemas/VoiceConfig",
            "description": "How the agent sounds \u2014 prebuilt voice id and speaking style. ``None``\nkeeps the server defaults for both."
          }
        },
        "required": [
          "type"
        ],
        "title": "InlineAgentConfig",
        "type": "object"
      },
      "InterruptionSensitivity": {
        "description": "How readily user speech interrupts the assistant mid-turn.",
        "enum": [
          "default",
          "high",
          "low"
        ],
        "title": "InterruptionSensitivity",
        "type": "string"
      },
      "MintTokenRequest": {
        "additionalProperties": false,
        "description": "Ask for a token bound to one of the developer's end users.",
        "properties": {
          "external_user_id": {
            "description": "Opaque developer-supplied identifier for the end user \u2014 letters, digits, dot, underscore, plus, at-sign, or hyphen (emails work), up to 128 chars. The same value binds to the same auto-provisioned project on repeat calls.",
            "maxLength": 128,
            "minLength": 1,
            "pattern": "^[A-Za-z0-9._+@-]+$",
            "type": "string"
          },
          "scopes": {
            "description": "Scopes for the minted token. Omit for the default: `realtime:start` alone. Beyond that, `realtime:read` and `realtime:delete` (both project-bound \u2014 the token reads or deletes only its own end user's sessions) and the connectors scopes may be requested. Anything else is rejected with 400.",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "type": "array"
          },
          "ttl_seconds": {
            "default": 86400,
            "description": "Token lifetime in seconds \u2014 between 60 and 86400 (24 hours, the default). Shorten it for tokens handed to short-lived contexts.",
            "maximum": 86400.0,
            "minimum": 60.0,
            "type": "integer"
          }
        },
        "required": [
          "external_user_id"
        ],
        "title": "MintTokenRequest",
        "type": "object"
      },
      "MintTokenResponse": {
        "description": "A minted end-user token: hand ``jwt`` to the browser or device.",
        "properties": {
          "expires_at": {
            "description": "When the token stops being accepted.",
            "format": "date-time",
            "type": "string"
          },
          "jwt": {
            "description": "The token itself. This is the only part a browser or device needs.",
            "type": "string"
          },
          "token_id": {
            "description": "Server-side id of this minted token; pass it to DELETE /api/v1/external/auth/token/{token_id} to revoke early.",
            "type": "string"
          }
        },
        "required": [
          "jwt",
          "expires_at",
          "token_id"
        ],
        "title": "MintTokenResponse",
        "type": "object"
      },
      "ModelTextEvent": {
        "description": "Streaming text-channel fragment from the model.\n\nDistinct from ``transcript``: this is text the model emits alongside its\naudio output, not a transcription of the audio itself. In audio sessions\nthe upstream may write function-call narration here that the model did\nNOT speak; in text-only sessions it carries the model's response.\nConsumers building a \"what was spoken\" transcript should subscribe to\n``TranscriptDeltaEvent`` instead.",
        "properties": {
          "is_final": {
            "default": false,
            "description": "Whether this closes the text response.",
            "type": "boolean"
          },
          "text": {
            "description": "The fragment of model text emitted since the previous event.",
            "type": "string"
          },
          "type": {
            "const": "model-text",
            "default": "model-text",
            "description": "The event type. Always ``model-text``.",
            "type": "string"
          }
        },
        "required": [
          "text",
          "type"
        ],
        "title": "ModelTextEvent",
        "type": "object"
      },
      "NoiseCancellation": {
        "description": "Which filter cleans the user's inbound audio before the model hears it.\n\n``denoise`` removes non-speech noise and keeps every voice in the room \u2014\nthe mode for a microphone several people share. ``voice_focus`` also\nremoves competing *voices*, keeping only the one it judges primary, which\nis what a single-speaker setup wants and what a shared microphone must\navoid: to it, the second person is background.\n\nThe filter sits ahead of the model on the inbound path, so it also shapes\nwhat the model's own turn-taking hears. ``off`` is the default \u2014 an\nintegrator who never asked for filtering should not pay its endpointing\ncost.",
        "enum": [
          "off",
          "denoise",
          "voice_focus"
        ],
        "title": "NoiseCancellation",
        "type": "string"
      },
      "OpenAILiveDelegation": {
        "description": "Where GPT Live sends the work it decides a turn needs. The voice\nmodel itself only listens and speaks.",
        "enum": [
          "responses",
          "client",
          "cosmo"
        ],
        "title": "OpenAILiveDelegation",
        "type": "string"
      },
      "OpenAILiveModel": {
        "additionalProperties": false,
        "description": "OpenAI's GPT Live full-duplex voice model. It listens and speaks at\nonce and decides itself when each turn starts and ends, so no turn\ndetector is tunable here; tool calls and reasoning are delegated to a\nbackend Responses model, which is what the knobs configure. Audio only:\na session on it ignores video and screen frames. A ``voice_\u2026`` id on the agent's ``voice`` selects an authorized\ncustom voice.",
        "properties": {
          "delegation": {
            "$ref": "#/components/schemas/OpenAILiveDelegation",
            "description": "Who does the work the voice model hands off. ``None`` is\n``responses``. Under ``client`` and ``cosmo`` the ``responses_*`` knobs\nare unused and the agent may declare no tools."
          },
          "max_output_tokens": {
            "description": "Cap on tokens one delegated response may generate. ``None`` keeps\nOpenAI's default.",
            "maximum": 32768.0,
            "minimum": 16.0,
            "type": "integer"
          },
          "model_id": {
            "description": "Concrete GPT Live model to run. ``None`` runs the provider default.",
            "type": "string"
          },
          "parallel_tool_calls": {
            "description": "Whether one delegated turn may call several tools at once. ``None``\nkeeps OpenAI's default.",
            "type": "boolean"
          },
          "provider": {
            "const": "openai_live",
            "description": "Names the provider this block configures. Always ``openai_live``;\nthe SDKs stamp it, so you never write it yourself.",
            "type": "string"
          },
          "reasoning_effort": {
            "$ref": "#/components/schemas/OpenAILiveReasoningEffort",
            "description": "How hard the Responses model reasons on delegated work. ``None``\nkeeps OpenAI's default."
          },
          "responses_instructions": {
            "description": "Instructions for the Responses model, distinct from the voice model's.\n``None`` gives it the agent's own instructions.",
            "maxLength": 16384,
            "type": "string"
          },
          "responses_model": {
            "description": "The Responses model tool calls and reasoning are delegated to, from\nthe server's allowlist of small tiers; a model outside it is rejected at\nsession start. ``None`` runs the provider default.",
            "type": "string"
          },
          "service_tier": {
            "$ref": "#/components/schemas/OpenAILiveServiceTier",
            "description": "OpenAI processing tier for delegated work. ``None`` keeps\nOpenAI's default."
          },
          "tool_choice": {
            "$ref": "#/components/schemas/OpenAILiveToolChoice",
            "description": "Whether a delegated turn must call a tool. ``None`` lets the model\ndecide (``auto``)."
          },
          "verbosity": {
            "$ref": "#/components/schemas/OpenAILiveVerbosity",
            "description": "How much the Responses model writes back for the voice model to say.\n``None`` keeps OpenAI's default."
          }
        },
        "required": [
          "provider"
        ],
        "title": "OpenAILiveModel",
        "type": "object"
      },
      "OpenAILiveReasoningEffort": {
        "description": "How hard the Responses model behind GPT Live reasons on delegated\nwork (tool calls and lookups). Lower effort answers sooner; ``None``\nkeeps OpenAI's default.",
        "enum": [
          "minimal",
          "low",
          "medium",
          "high"
        ],
        "title": "OpenAILiveReasoningEffort",
        "type": "string"
      },
      "OpenAILiveServiceTier": {
        "description": "OpenAI processing tier for the Responses model behind GPT Live.",
        "enum": [
          "auto",
          "default",
          "flex",
          "priority"
        ],
        "title": "OpenAILiveServiceTier",
        "type": "string"
      },
      "OpenAILiveToolChoice": {
        "description": "Whether the Responses model behind GPT Live must call a tool on each\ndelegated turn.",
        "enum": [
          "auto",
          "required",
          "none"
        ],
        "title": "OpenAILiveToolChoice",
        "type": "string"
      },
      "OpenAILiveVerbosity": {
        "description": "How much the Responses model behind GPT Live writes back for the\nvoice model to say. ``LOW`` keeps delegated answers short.",
        "enum": [
          "low",
          "medium",
          "high"
        ],
        "title": "OpenAILiveVerbosity",
        "type": "string"
      },
      "OpenAIMiniModel": {
        "additionalProperties": false,
        "description": "The OpenAI-Realtime mini tier \u2014 the same API as\n``OpenAIModel`` on a faster, cheaper model, and equally\nuntunable today.",
        "properties": {
          "model_id": {
            "description": "Concrete mini-tier model to run. ``None`` runs the provider default.",
            "type": "string"
          },
          "provider": {
            "const": "openai_mini",
            "description": "Names the provider this block configures. Always ``openai_mini``;\nthe SDKs stamp it, so you never write it yourself.",
            "type": "string"
          }
        },
        "required": [
          "provider"
        ],
        "title": "OpenAIMiniModel",
        "type": "object"
      },
      "OpenAIModel": {
        "additionalProperties": false,
        "description": "The OpenAI-Realtime provider with its knobs. OpenAI Realtime pins its\nown sampling and token limits, so only turn-taking is tunable here.\n\n``turn_detection`` selects which detector runs and decides which of the\nremaining knobs apply: ``eagerness`` belongs to ``semantic_vad``, the two\nwindow knobs to ``server_vad``. Sending a knob from the other mode is\nrejected at session start rather than silently ignored.",
        "properties": {
          "eagerness": {
            "$ref": "#/components/schemas/SemanticEagerness",
            "description": "How eagerly ``semantic_vad`` closes the user's turn \u2014 ``high`` answers\nsooner, ``low`` waits longer for them to continue. Valid only with\n``turn_detection: semantic_vad``. ``None`` keeps the provider default."
          },
          "model_id": {
            "description": "Concrete OpenAI Realtime model to run. ``None`` runs the provider\ndefault. A model id that is not an OpenAI model is rejected at session\nstart.",
            "type": "string"
          },
          "prefix_padding_ms": {
            "description": "Audio, in milliseconds, kept from before speech was detected. Valid only\nwith ``server_vad``. ``None`` keeps the server's default.",
            "maximum": 5000.0,
            "minimum": 0.0,
            "type": "integer"
          },
          "provider": {
            "const": "openai",
            "description": "Names the provider this block configures. Always ``openai``;\nthe SDKs stamp it, so you never write it yourself.",
            "type": "string"
          },
          "silence_duration_ms": {
            "description": "Silence, in milliseconds, that ends the user's turn. Valid only with\n``server_vad``. ``None`` keeps the server's default.",
            "maximum": 5000.0,
            "minimum": 0.0,
            "type": "integer"
          },
          "turn_detection": {
            "$ref": "#/components/schemas/TurnDetectionMode",
            "description": "Which turn detector runs. ``semantic_vad`` ends the turn as soon as the\nutterance reads as complete rather than after a fixed silence window.\n``None`` keeps the provider default (``server_vad``)."
          }
        },
        "required": [
          "provider"
        ],
        "title": "OpenAIModel",
        "type": "object"
      },
      "PointAtObjectToolSpec": {
        "additionalProperties": false,
        "description": "Opt-in to the server-executed object locator, which returns points.\n\nThe sibling of ``detect_objects``: a marked point says one thing where\na box around a leaf includes everything behind it. Same zero-config and\navailability terms.",
        "properties": {
          "kind": {
            "const": "point_at_object",
            "default": "point_at_object",
            "description": "The tool kind. Always ``point_at_object``.",
            "type": "string"
          }
        },
        "required": [
          "kind"
        ],
        "title": "PointAtObjectToolSpec",
        "type": "object"
      },
      "PongEvent": {
        "description": "Reply to ``ClientPing``.",
        "properties": {
          "type": {
            "const": "pong",
            "default": "pong",
            "description": "The event type. Always ``pong``.",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "title": "PongEvent",
        "type": "object"
      },
      "ProviderCapabilities": {
        "description": "Which realtime model providers the caller may select per session.\n\nClients that surface a provider picker should hide it when\n``openai_provider_available`` is false \u2014 a picker the server would\nsilently override is worse than no picker.",
        "properties": {
          "openai_provider_available": {
            "description": "Whether this workspace may select the OpenAI providers (``openai``,\n``openai_mini``, ``openai_live``): the server has an OpenAI key and the\nplan includes them.",
            "type": "boolean"
          }
        },
        "required": [
          "openai_provider_available"
        ],
        "title": "ProviderCapabilities",
        "type": "object"
      },
      "ReadyEvent": {
        "description": "Sent after the upstream session is established and the agent is ready.",
        "properties": {
          "agent": {
            "$ref": "#/components/schemas/ResolvedAgent",
            "description": "Resolved registry-agent summary (see ``ResolvedAgent``).\n``None`` for inline / default-agent sessions."
          },
          "max_session_seconds": {
            "description": "Effective server-enforced session duration cap, so clients can render\ntheir own countdown. ``None`` = no cap. The server also pushes\n``session-ending-soon`` near the deadline and ``session-ended`` at\ncutoff.",
            "type": "integer"
          },
          "rejected_tools": {
            "description": "Tools this session could not get \u2014 a registered tool the deployment\nor workspace cannot run right now \u2014 with the reason for each. The session\nstarts without them, so check this to see what it is actually running.\n\nOnly availability drops appear here. A spec the server considers\nmalformed \u2014 a bad schema, a duplicate or reserved name, a ``kind`` this\nflow does not execute \u2014 rejects the whole session start with a 422\n(``invalid_tool_config``) instead, and never reaches this list.",
            "items": {
              "$ref": "#/components/schemas/RejectedTool"
            },
            "type": "array"
          },
          "session_id": {
            "description": "Server-assigned id for this session. Clients persist it and pass it back\nas ``experimental.resume_session_id`` on a fresh ``session-config`` to\nresume after a disconnect.",
            "format": "uuid",
            "type": "string"
          },
          "type": {
            "const": "ready",
            "default": "ready",
            "description": "The event type. Always ``ready``.",
            "type": "string"
          }
        },
        "required": [
          "session_id",
          "type"
        ],
        "title": "ReadyEvent",
        "type": "object"
      },
      "ReconnectingEvent": {
        "description": "Server is transparently rotating the upstream session.\n\nEmitted after the upstream signals an imminent shutdown when the server\nhas cached a resumption handle and is reopening the model session. The\ntransport and session state survive the swap; clients can show a brief\n\"reconnecting\u2026\" indicator and otherwise stay put.",
        "properties": {
          "seconds_remaining": {
            "description": "Rough seconds until the swap completes, when the upstream reports\nit. ``None`` when it does not.",
            "type": "number"
          },
          "type": {
            "const": "reconnecting",
            "default": "reconnecting",
            "description": "The event type. Always ``reconnecting``.",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "title": "ReconnectingEvent",
        "type": "object"
      },
      "RejectedTool": {
        "description": "One tool spec the server refused, with the reason \u2014 so a client can\nlog *why* a spec was dropped instead of debugging silence.",
        "properties": {
          "name": {
            "description": "What the dropped tool was called \u2014 a client tool's declared name, or a\nserver tool's wire kind.",
            "type": "string"
          },
          "reason": {
            "description": "Why it was unavailable, e.g. a capability this workspace has not\nenabled. Free text for logs and display, not a stable code to match\non.",
            "type": "string"
          }
        },
        "required": [
          "name",
          "reason"
        ],
        "title": "RejectedTool",
        "type": "object"
      },
      "ResolvedAgent": {
        "description": "Resolved-agent summary echoed on ``ready`` when the session referenced a\nregistry agent (``agent.name``). Informational only \u2014 never authoritative;\nclients don't act on it.",
        "properties": {
          "name": {
            "description": "The registry agent the session resolved against.",
            "type": "string"
          },
          "tools": {
            "description": "Final effective tool names (registry tools \u222a client-declared tools,\nby name).",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "name"
        ],
        "title": "ResolvedAgent",
        "type": "object"
      },
      "RevokeTokenResponse": {
        "description": "Result of revoking a minted end-user token.",
        "properties": {
          "revoked": {
            "description": "Whether the token was revoked. Revoking one already revoked, or one\nthat has expired, still reports ``true`` \u2014 the end state is the same.",
            "type": "boolean"
          }
        },
        "required": [
          "revoked"
        ],
        "title": "RevokeTokenResponse",
        "type": "object"
      },
      "Say": {
        "additionalProperties": false,
        "description": "Idle-message action for a server hook: `text` = exact words,\n`prompt` = model-generated per instruction, both unset = free model speech.",
        "properties": {
          "prompt": {
            "description": "Instruction the model composes its line from, for wording that follows\nwhat has been said so far. Mutually exclusive with ``text``.",
            "maxLength": 4096,
            "type": "string"
          },
          "text": {
            "description": "Exact words for the assistant to speak. Mutually exclusive with\n``prompt``.",
            "maxLength": 4096,
            "type": "string"
          },
          "type": {
            "const": "say",
            "default": "say",
            "description": "The action type. Always ``say``.",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "title": "Say",
        "type": "object"
      },
      "ScreenLocateToolSpec": {
        "additionalProperties": false,
        "description": "Opt-in to the server-executed screen locator.\n\nResolves a description to an element on the client's shared screen and\nhands the model an opaque handle for it, which the model passes to\nwhichever screen renderer the client declared\n(``cosmo_sdk_screen_click_element`` /\n``cosmo_sdk_screen_highlight_element``). Not authorable: SDKs emit this\nentry mechanically when the host supplies a screen-capture handler, whose\n``screen_capture`` RPC the locator drives. Zero-config;\nunknown fields are a schema error.\n\nThe locator itself has no availability gate.\n``cosmo_sdk_screen_click_element`` does \u2014 clicking acts on the user's\nmachine, so it stays behind the desktop-control policy, and a session that\ncannot run it starts without it and reports the drop on\n``ready.rejected_tools`` under that name.",
        "properties": {
          "kind": {
            "const": "screen_locate",
            "default": "screen_locate",
            "description": "The tool kind. Always ``screen_locate``.",
            "type": "string"
          }
        },
        "required": [
          "kind"
        ],
        "title": "ScreenLocateToolSpec",
        "type": "object"
      },
      "SdkInfo": {
        "additionalProperties": false,
        "description": "Self-reported identity of the SDK that opened the session.\n\n``name`` is the SDK's registry package name (\"cosmo-ai-sdk\" on PyPI,\n\"cosmo-ai\" on npm, \"cosmo-swift-sdk\" for SwiftPM) \u2014 with ``version``,\nexactly what is installed. Deployed clients keep sending the name they\nwere built with; a package rename maps the old name onto the new line\nserver-side.\n\nThe pair is a claim, not a fact: it feeds telemetry and, for packages\nwe ship, support decisions \u2014 never authorization or billing. An unknown\nor absent identity is bucketed, never refused.",
        "properties": {
          "name": {
            "description": "Registry package name of the SDK, e.g. ``cosmo-ai-sdk``.",
            "maxLength": 64,
            "type": "string"
          },
          "version": {
            "description": "Installed version of that package.",
            "maxLength": 32,
            "type": "string"
          }
        },
        "required": [
          "name",
          "version"
        ],
        "title": "SdkInfo",
        "type": "object"
      },
      "SemanticEagerness": {
        "description": "How eagerly OpenAI's ``semantic_vad`` closes the user's turn.\n\n``low`` waits longer for the user to continue, ``high`` responds sooner;\n``auto`` is the provider default and behaves like ``medium``.",
        "enum": [
          "low",
          "medium",
          "high",
          "auto"
        ],
        "title": "SemanticEagerness",
        "type": "string"
      },
      "ServerEnvelope": {
        "description": "Generic chunked carrier for any oversized server message.\n\nMirror of ``ClientEnvelope`` for the server\u2192client direction.\nThe server wraps any ``ServerMessage`` whose serialized JSON\nexceeds the transport threshold into a sequence of these chunks; the SDK\nbuffers by ``envelope_id``, base64-decodes + concatenates, and\nre-dispatches the inner message through the same handler path as\nun-chunked messages.\n\n``data`` is base64 over the UTF-8 bytes of the inner message JSON for\nthe same reason as the client side: keeps every chunk ASCII-safe so\nbyte-boundary splits never land mid-codepoint.",
        "properties": {
          "data": {
            "description": "This chunk's slice of the inner message, base64-encoded.",
            "type": "string"
          },
          "envelope_id": {
            "description": "Groups the chunks of one message, so concurrent envelopes do not\ninterleave.",
            "type": "string"
          },
          "seq": {
            "description": "Position of this chunk, ``0`` to ``total - 1``.",
            "type": "integer"
          },
          "total": {
            "description": "How many chunks the message was split into.",
            "type": "integer"
          },
          "type": {
            "const": "server-envelope-chunk",
            "default": "server-envelope-chunk",
            "description": "The message type. Always ``server-envelope-chunk``.",
            "type": "string"
          }
        },
        "required": [
          "envelope_id",
          "seq",
          "total",
          "data",
          "type"
        ],
        "title": "ServerEnvelope",
        "type": "object"
      },
      "ServerToolSpec": {
        "additionalProperties": false,
        "description": "Opt-in to one server-executed tool by its dot-namespaced name\n(e.g. ``\"cosmo.web_search\"``).\n\nDeprecated and no longer executed anywhere: typed per-tool kinds\n(``web_search``, ``examine_image``, ``detect``, ``point``,\n``end_call``) supersede the generic name-reference, and the flow that\nhonored it is gone \u2014 a config carrying one is rejected at session start\nwith a typed 422 (``invalid_tool_config``). ``cosmo.view_state`` /\n``cosmo.set_state`` have no typed kinds yet, so they are unreachable\nuntil they gain them.\n\nServer tools execute server-side; the session observes them through the\n``tool-call`` / ``tool-dispatch-started`` / ``tool-result`` lifecycle.",
        "properties": {
          "kind": {
            "const": "server",
            "default": "server",
            "description": "The tool kind. Always ``server`` \u2014 the deprecated form, which\nsession start rejects.",
            "type": "string"
          },
          "name": {
            "description": "Dot-namespaced name of the server tool, e.g. ``cosmo.web_search``.\nKept so the wire shape still decodes; use the tool's own typed kind\ninstead, since a config carrying this one is refused.",
            "maxLength": 128,
            "type": "string"
          }
        },
        "required": [
          "name",
          "kind"
        ],
        "title": "ServerToolSpec",
        "type": "object"
      },
      "SessionArtifact": {
        "description": "One downloadable recording of a finished session.\n\n``download_url`` is signed and expires; re-request the listing for a\nfresh one rather than storing it.",
        "properties": {
          "content_type": {
            "description": "Media type of the file the URL serves.",
            "type": "string"
          },
          "download_url": {
            "description": "Signed URL for the file. It expires \u2014 re-request the listing for a\nfresh one rather than storing it.",
            "type": "string"
          },
          "filename": {
            "description": "Suggested filename for saving the download.",
            "type": "string"
          },
          "kind": {
            "$ref": "#/components/schemas/SessionArtifactKind",
            "description": "Which composed recording this is."
          }
        },
        "required": [
          "kind",
          "content_type",
          "download_url",
          "filename"
        ],
        "title": "SessionArtifact",
        "type": "object"
      },
      "SessionArtifactKind": {
        "description": "Which composed recording a download URL points at.",
        "enum": [
          "audio",
          "stereo_audio",
          "video"
        ],
        "title": "SessionArtifactKind",
        "type": "string"
      },
      "SessionArtifacts": {
        "description": "The composed recordings a session left behind.\n\nOnly artifacts already materialized in storage are listed, so a\nsession that recorded no video, or one still running, reports fewer\nthan a completed audio+video call. ``expires_at`` applies to every\n``download_url`` in ``artifacts``.",
        "properties": {
          "artifacts": {
            "description": "Only the recordings already materialized in storage, so a session that\nrecorded no video \u2014 or one still running \u2014 lists fewer than a completed\naudio and video call.",
            "items": {
              "$ref": "#/components/schemas/SessionArtifact"
            },
            "type": "array"
          },
          "expires_at": {
            "description": "When every ``download_url`` above stops working, epoch seconds.",
            "type": "number"
          },
          "session_id": {
            "description": "The session these recordings belong to.",
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "session_id",
          "artifacts",
          "expires_at"
        ],
        "title": "SessionArtifacts",
        "type": "object"
      },
      "SessionConfig": {
        "additionalProperties": false,
        "description": "Session-start payload for external sessions \u2014 carried on the HTTP\nsession-start request (or as the first frame on the WS connect path).\nThe server replies with ``ReadyEvent`` once the agent is up.\n\nSplit into the two concerns of a session: ``agent`` (the persona \u2014 what the\nmodel is) and ``session`` (per-run transport options). ``sdk`` identifies\nthe client (see ``SdkInfo``).",
        "properties": {
          "agent": {
            "description": "The persona to run: defined inline, or a reference to a workspace\ncatalog agent. Omitted runs the default agent.",
            "discriminator": {
              "mapping": {
                "catalog": "#/components/schemas/CatalogAgentConfig",
                "inline": "#/components/schemas/InlineAgentConfig"
              },
              "propertyName": "type"
            },
            "oneOf": [
              {
                "$ref": "#/components/schemas/CatalogAgentConfig"
              },
              {
                "$ref": "#/components/schemas/InlineAgentConfig"
              }
            ]
          },
          "sdk": {
            "$ref": "#/components/schemas/SdkInfo",
            "description": "Which SDK and version opened the session."
          },
          "session": {
            "$ref": "#/components/schemas/SessionParams",
            "description": "Per-run options for this session \u2014 storage consents, duration cap,\nexperimental knobs. Omitted takes every default."
          },
          "type": {
            "const": "session-config",
            "default": "session-config",
            "description": "The message type. Always ``session-config``.",
            "type": "string"
          }
        },
        "required": [
          "sdk",
          "type"
        ],
        "title": "SessionConfig",
        "type": "object"
      },
      "SessionEndedEvent": {
        "description": "The server ended the session on purpose. Published just before the\ntransport tears down so clients can render a clean terminal state\ninstead of treating the room drop as a failure. Delivery is\nbest-effort \u2014 the room close may still arrive first.",
        "properties": {
          "reason": {
            "description": "Stable slug (e.g. ``\"max_session_duration\"``); same vocabulary as\n``SessionEndingSoonEvent.reason``.",
            "type": "string"
          },
          "type": {
            "const": "session-ended",
            "default": "session-ended",
            "description": "The event type. Always ``session-ended``.",
            "type": "string"
          }
        },
        "required": [
          "reason",
          "type"
        ],
        "title": "SessionEndedEvent",
        "type": "object"
      },
      "SessionEndingSoonEvent": {
        "description": "The server will end this session shortly (e.g. the max-duration cap\nis about to fire). Clients may show a countdown; the session keeps\nrunning until ``session-ended``.",
        "properties": {
          "reason": {
            "description": "Stable slug for why the session is ending (e.g.\n``\"max_session_duration\"``). The vocabulary is additive: match the\nslugs you know and treat an unfamiliar one as a plain end.",
            "type": "string"
          },
          "seconds_remaining": {
            "description": "Seconds until the session is cut, for a countdown.",
            "type": "number"
          },
          "type": {
            "const": "session-ending-soon",
            "default": "session-ending-soon",
            "description": "The event type. Always ``session-ending-soon``.",
            "type": "string"
          }
        },
        "required": [
          "seconds_remaining",
          "reason",
          "type"
        ],
        "title": "SessionEndingSoonEvent",
        "type": "object"
      },
      "SessionImportRequest": {
        "additionalProperties": false,
        "description": "Bundle for seeding a completed call: transcript + metadata + optional audio.",
        "properties": {
          "audio_wav_base64": {
            "description": "Optional mono WAV recording, base64-encoded.",
            "type": "string"
          },
          "ended_at": {
            "description": "Call end time, epoch seconds.",
            "type": "number"
          },
          "provider": {
            "description": "Which model provider ran the imported call, when it is known.",
            "enum": [
              "gemini",
              "openai",
              "openai_mini",
              "openai_live",
              "grok",
              "cosmo_voice_personaplex",
              "cosmo_voice_ultravox"
            ],
            "type": "string"
          },
          "started_at": {
            "description": "Call start time, epoch seconds.",
            "type": "number"
          },
          "title": {
            "description": "Display title for the session.",
            "type": "string"
          },
          "transcript": {
            "description": "Ordered speech turns (user/assistant).",
            "items": {
              "$ref": "#/components/schemas/ImportedTranscriptTurn"
            },
            "type": "array"
          }
        },
        "required": [
          "started_at"
        ],
        "title": "SessionImportRequest",
        "type": "object"
      },
      "SessionParams": {
        "additionalProperties": false,
        "description": "Per-run, transport-level options for one session \u2014 continuity and other\nknobs that vary run-to-run for the same agent. Audio config lives on the\n``agent`` block (it's part of the agent, configured once).",
        "properties": {
          "experimental": {
            "$ref": "#/components/schemas/ExperimentalParams",
            "description": "Unstable opt-in knobs (see ``ExperimentalParams``).\nMay change shape between releases."
          },
          "max_session_seconds": {
            "description": "Requested wall-clock cap on the session, in seconds. The server\nresolves the effective cap as the minimum of this and its own limits \u2014\ncallers can only shorten, never extend. The effective value is echoed\non ``ready``; the server pushes ``session-ending-soon`` near the\ndeadline and ``session-ended`` at cutoff.",
            "maximum": 14400.0,
            "minimum": 60.0,
            "type": "integer"
          },
          "store_audio": {
            "description": "Persist this session's audio. Narrowing only: a session may ask for\nless storage than the account's consents allow, never more. Unset defers\nto ``store_recording``, then to those consents.",
            "type": "boolean"
          },
          "store_recording": {
            "description": "Persist this session's recording artifacts (audio/video/transcript/tool\nevents) server-side. ``False`` writes nothing for the run. Unset stores as\nmuch as the account's consents allow. The per-artifact fields below take\nprecedence over this one where both are sent.",
            "type": "boolean"
          },
          "store_transcript": {
            "description": "Persist this session's transcript and tool-call events. Same contract\nas ``store_audio``.",
            "type": "boolean"
          },
          "store_video": {
            "description": "Persist this session's screen-share video. Same contract as\n``store_audio``. Screenshots have no field of their own and follow\n``store_recording``, so turning this off does not stop them.",
            "type": "boolean"
          }
        },
        "title": "SessionParams",
        "type": "object"
      },
      "SessionRecord": {
        "description": "Developer-facing session record for the external (API-key) data-out\nendpoints. Carries only product-agnostic fields \u2014 the workspace and the\nend user are resolved from the credential, never echoed back.",
        "properties": {
          "ended_at": {
            "description": "When it ended, epoch seconds. Absent while it is still running.",
            "type": "number"
          },
          "has_resumption_handle": {
            "description": "Whether this session stored a resumption handle. Not a promise that a\nresume will use it: the server decides that when the resume is requested,\nand falls back to seeding the new session with the prior transcript for a\nsession that ended too long ago. Either way the request is the same \u2014\npass the id as ``experimental.resume_session_id``.",
            "type": "boolean"
          },
          "id": {
            "description": "Server-assigned session identifier.",
            "format": "uuid",
            "type": "string"
          },
          "provider": {
            "description": "Which model provider ran it, once resolved.",
            "enum": [
              "gemini",
              "openai",
              "openai_mini",
              "openai_live",
              "grok",
              "cosmo_voice_personaplex",
              "cosmo_voice_ultravox"
            ],
            "type": "string"
          },
          "started_at": {
            "description": "When the session started, epoch seconds.",
            "type": "number"
          },
          "status": {
            "$ref": "#/components/schemas/SessionStatus",
            "description": "Where the session ended up: ``active`` while it runs, ``completed``\nwhen it ended normally, ``error`` when it did not."
          }
        },
        "required": [
          "id",
          "started_at",
          "status",
          "has_resumption_handle"
        ],
        "title": "SessionRecord",
        "type": "object"
      },
      "SessionResponse": {
        "description": "Connection credentials for a started session.\n\nThe client joins the LiveKit room at ``livekit_url`` with ``token``;\n``session_id`` correlates the session across API calls and session\nevents.",
        "properties": {
          "livekit_url": {
            "description": "LiveKit room URL to connect to.",
            "type": "string"
          },
          "room_name": {
            "description": "LiveKit room name.",
            "type": "string"
          },
          "session_id": {
            "description": "Server-minted session identifier.",
            "type": "string"
          },
          "timings": {
            "$ref": "#/components/schemas/SessionStartTimings",
            "description": "Server-side session-start phase breakdown (ms)."
          },
          "token": {
            "description": "Short-lived participant join token.",
            "type": "string"
          }
        },
        "required": [
          "livekit_url",
          "token",
          "room_name",
          "session_id"
        ],
        "title": "SessionResponse",
        "type": "object"
      },
      "SessionResponsiveness": {
        "description": "Session-level rollups over the turns, so the common question \u2014\nwas this session slow \u2014 is one read rather than an aggregation.\n\n``first_response_latency_ms`` covers session and provider warm-up and\nonly exists for turn one; it excludes a pre-turn greeting, and\n``first_response_agent_greeted`` reports whether there was one.\n\n``first_response_anchor_measured`` says where that latency was timed\nfrom: true anchors it on measured caller audio, false on session state\ninstead, and null means a blob old enough not to have recorded which.\nThe two anchors answer different questions, so a series spanning both\nis not comparable to itself \u2014 read the latency with this, not alone.",
        "properties": {
          "first_response_agent_greeted": {
            "description": "Whether the agent opened with a greeting. A greeting is excluded from\n``first_response_latency_ms``, so this says whether there was one.",
            "type": "boolean"
          },
          "first_response_anchor_measured": {
            "description": "Where that first latency was timed from: ``true`` anchors it on\nmeasured caller audio, ``false`` on session state instead, and null means\na record old enough not to have written it down. The two anchors answer\ndifferent questions, so a series spanning both is not comparable to\nitself \u2014 read the latency together with this, never alone.",
            "type": "boolean"
          },
          "first_response_latency_ms": {
            "description": "How long the caller waited for the agent's first reply. Covers session\nand provider warm-up, so it is not comparable with the medians above, and\nexists only for turn one.",
            "type": "integer"
          },
          "median_eou_delay_ms": {
            "description": "Median endpointing delay: how long the detector took to decide each\nturn had ended.",
            "type": "integer"
          },
          "median_generation_to_playout_ms": {
            "description": "Median gap between the model generating and audio reaching the\ntransport.",
            "type": "integer"
          },
          "median_response_ms": {
            "description": "Median time to first token across the session's turns.",
            "type": "integer"
          },
          "p95_generation_to_playout_ms": {
            "description": "95th percentile of that same gap.",
            "type": "integer"
          },
          "p95_response_ms": {
            "description": "95th-percentile time to first token \u2014 the tail a caller notices.",
            "type": "integer"
          }
        },
        "title": "SessionResponsiveness",
        "type": "object"
      },
      "SessionStartTimings": {
        "description": "Server-side phase breakdown of session start (milliseconds).\n\nMirrors the ``starter_*`` fields of the \"realtime session dispatched\"\nlog line. Echoed to the client so it can emit one joined\nstartup-waterfall event (server + client phases) keyed by session_id,\ninstead of leaving the join to log archaeology.\n\nThe server produces these on ``/session/start``; the same shape comes\nback untrusted on ``connect-timings``, so the bounds hold on both paths.",
        "properties": {
          "db_insert_ms": {
            "description": "Recording the session row.",
            "minimum": 0.0,
            "type": "integer"
          },
          "dispatch_ms": {
            "description": "Dispatching the agent to the room. Reports ``0`` when dispatch runs\nafter the response, where it costs the client nothing.",
            "minimum": 0.0,
            "type": "integer"
          },
          "mint_tokens_ms": {
            "description": "Minting the room join token.",
            "minimum": 0.0,
            "type": "integer"
          },
          "project_check_ms": {
            "description": "Resolving and authorizing the calling project.",
            "minimum": 0.0,
            "type": "integer"
          },
          "provider_resolve_ms": {
            "description": "Choosing the model provider and confirming it is available here.",
            "minimum": 0.0,
            "type": "integer"
          },
          "resolve_ms": {
            "description": "Version check, project, provider, tools and limits, resolved\ntogether and reported as one number. The phases folded into it report\n``0`` in their own fields rather than a fabricated split, and\n``dispatch_ms`` reports ``0`` too \u2014 dispatch runs after the response, so\nit costs the client nothing.",
            "minimum": 0.0,
            "type": "integer"
          },
          "total_ms": {
            "description": "The whole server-side start, end to end. Not the sum of the phases\nabove \u2014 phases folded into ``resolve_ms`` report ``0`` individually.",
            "minimum": 0.0,
            "type": "integer"
          },
          "version_check_ms": {
            "description": "Checking the client's SDK version against the supported floor.",
            "minimum": 0.0,
            "type": "integer"
          }
        },
        "required": [
          "version_check_ms",
          "project_check_ms",
          "provider_resolve_ms",
          "db_insert_ms",
          "mint_tokens_ms",
          "dispatch_ms",
          "total_ms"
        ],
        "title": "SessionStartTimings",
        "type": "object"
      },
      "SessionStateWriteEvent": {
        "description": "First-party cosmo event: live session state after a ``set_state`` tool\nwrite, for rendering call progress in a client UI. Carries the\nfull canonical state, not a delta; ``stage`` is hoisted from\n``state[\"stage\"]`` and ``warnings`` are the advisory schema findings the\nmodel also saw in its tool result.",
        "properties": {
          "stage": {
            "description": "``state[\"stage\"]``, lifted out for clients that render call progress.\n``None`` when the state carries no stage.",
            "type": "string"
          },
          "state": {
            "additionalProperties": true,
            "description": "The whole state after the write, so the newest event supersedes the\nprevious one.",
            "type": "object"
          },
          "type": {
            "const": "cosmo.session-state",
            "default": "cosmo.session-state",
            "description": "The event type. Always ``cosmo.session-state``.",
            "type": "string"
          },
          "updated_keys": {
            "description": "Just the keys this write touched, for highlighting what changed.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "warnings": {
            "description": "Advisory schema findings from the write. The model saw these too;\nthey did not block it.",
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "type"
        ],
        "title": "SessionStateWriteEvent",
        "type": "object"
      },
      "SessionStatus": {
        "description": "Where a realtime session is in its lifecycle.",
        "enum": [
          "active",
          "completed",
          "error"
        ],
        "title": "SessionStatus",
        "type": "string"
      },
      "SessionTimeline": {
        "description": "When each turn of a session happened and how long its parts took.\n\nThe companion to the transcript: that endpoint reports what was said,\nthis one what happened and when. ``turns`` is ordered oldest first and\nis empty unless ``timeline_status`` is ``recorded`` \u2014 it carries the\nsame three states as a session's usage summary, which is written by\nthe same teardown pass.",
        "properties": {
          "responsiveness": {
            "$ref": "#/components/schemas/SessionResponsiveness",
            "description": "Session-level rollups over the turns, so \"was this session slow\" is one\nread rather than an aggregation."
          },
          "session_id": {
            "description": "The session this timeline belongs to.",
            "format": "uuid",
            "type": "string"
          },
          "started_at": {
            "description": "Session start, the origin every ``at`` is meaningful against \u2014 a\ntimeline is read as offsets, and without this one a caller would have to\nfetch the session record to compute them.",
            "type": "number"
          },
          "timeline_status": {
            "$ref": "#/components/schemas/UsageStatus",
            "description": "Whether the timeline exists yet. Carries the same three states as the\nusage summary, which the same teardown pass writes."
          },
          "turn_count": {
            "default": 0,
            "description": "Model-response turns the session recorded, taken from its stored\nsummary. Not the length of ``turns``: that list is empty on a session\nrecorded before per-turn spans were captured, and carries turn-detector\nrows the count never included.",
            "type": "integer"
          },
          "turns": {
            "default": [],
            "description": "The turns, oldest first. Empty unless ``timeline_status`` is\n``recorded``.",
            "items": {
              "$ref": "#/components/schemas/SessionTurn"
            },
            "type": "array"
          }
        },
        "required": [
          "session_id",
          "started_at",
          "timeline_status"
        ],
        "title": "SessionTimeline",
        "type": "object"
      },
      "SessionTokenUsage": {
        "description": "Token usage reported by the session's model provider, split by\ndirection and modality. The live ``cosmo.usage`` event's counters plus\nthe input and output totals, with the same cumulative semantics.",
        "properties": {
          "input_audio_tokens": {
            "default": 0,
            "description": "Audio the model was given.",
            "type": "integer"
          },
          "input_cached_tokens": {
            "default": 0,
            "description": "Input served from the provider's cache. Already counted in\n``input_tokens`` \u2014 a subset, not an addition.",
            "type": "integer"
          },
          "input_image_tokens": {
            "default": 0,
            "description": "Images the model was given.",
            "type": "integer"
          },
          "input_text_tokens": {
            "default": 0,
            "description": "Text the model was given.",
            "type": "integer"
          },
          "input_tokens": {
            "default": 0,
            "description": "Every input token, across all modalities.",
            "type": "integer"
          },
          "output_audio_tokens": {
            "default": 0,
            "description": "Audio the model produced. On a session running ``audio.output=false``\nthis depends on the provider: one with a native text-only mode produces\nnone, while one without keeps generating speech that is discarded, and\nthose tokens still accrue.",
            "type": "integer"
          },
          "output_text_tokens": {
            "default": 0,
            "description": "Text the model produced.",
            "type": "integer"
          },
          "output_tokens": {
            "default": 0,
            "description": "Every output token, across all modalities.",
            "type": "integer"
          },
          "total_tokens": {
            "default": 0,
            "description": "Input plus output, as the provider reports it.",
            "type": "integer"
          }
        },
        "title": "SessionTokenUsage",
        "type": "object"
      },
      "SessionTranscriptTurn": {
        "description": "One transcript line: timestamp (epoch seconds), speaker role, and text.\n\nOpen to unknown keys: this is also the transcript endpoint's response\nmodel, and the server->client direction stays tolerant. The import\nrequest nests a strict variant instead.",
        "properties": {
          "role": {
            "description": "Who spoke it.",
            "enum": [
              "user",
              "assistant"
            ],
            "type": "string"
          },
          "text": {
            "description": "What was said.",
            "type": "string"
          },
          "ts": {
            "description": "When the line was spoken, epoch seconds.",
            "type": "number"
          }
        },
        "required": [
          "ts",
          "role",
          "text"
        ],
        "title": "TranscriptTurn",
        "type": "object"
      },
      "SessionTurn": {
        "description": "One turn's server-measured spans, in milliseconds.\n\n``source`` says which mechanism produced the row and therefore which\nspans are set, so a null is \"this mechanism does not measure that\"\nrather than \"this turn was instant\":\n\n* ``realtime_model`` \u2014 one row per model reply. ``ttft_ms`` and\n  ``response_duration_ms`` are provider-reported;\n  ``generation_to_playout_ms`` and ``playout_duration_ms`` are taken\n  from state transitions on one process clock.\n* ``pipeline_eou`` \u2014 the turn detector's own endpointing figures,\n  ``end_of_utterance_delay_ms`` and ``transcription_delay_ms``.\n\n``listening_to_generation_ms`` is the whole window from the agent\nreturning to listening until the provider's first generation packet.\nIt contains the caller's own think time and utterance as well as the\nturn-detection decision, so it is **not** a measure of how long the\nagent took to react \u2014 reach for ``ttft_ms`` for that.\n\nEvery span is measured on the server, whose clock stops at the frame\nhanded to the transport. None of them include the listener's playout\nhop; only the client is on that side of the network.",
        "properties": {
          "at": {
            "description": "When the turn happened, epoch seconds \u2014 the same clock as the\ntimeline's ``started_at``. Subtract that to read the timeline as\noffsets.",
            "type": "number"
          },
          "end_of_utterance_delay_ms": {
            "description": "The turn detector's own endpointing figure \u2014 how long after the\nspeaker stopped it decided the turn had ended. Set on ``pipeline_eou``\nrows.",
            "type": "integer"
          },
          "generation_to_playout_ms": {
            "description": "From the first generated packet to the first frame handed to the\ntransport, taken from state transitions on one process clock.",
            "type": "integer"
          },
          "index": {
            "description": "Position of this turn among the model's replies, counting from one.\nNull on a row the turn detector produced rather than the model, which is\nnot numbered.",
            "type": "integer"
          },
          "listening_to_generation_ms": {
            "description": "From the agent returning to listening until the provider's first\ngeneration packet. Contains the caller's own think time and utterance as\nwell as the turn-detection decision, so it is not a measure of agent\nreaction time \u2014 read ``ttft_ms`` for that.",
            "type": "integer"
          },
          "playout_duration_ms": {
            "description": "How long the agent's audio played for, on the same clock.",
            "type": "integer"
          },
          "response_duration_ms": {
            "description": "How long the provider spent generating the reply. Set on\n``realtime_model`` rows.",
            "type": "integer"
          },
          "source": {
            "description": "Which mechanism produced this row, and therefore which spans below are\nset. A null span means this mechanism does not measure that, not that the\nturn was instant.",
            "enum": [
              "realtime_model",
              "pipeline_eou"
            ],
            "type": "string"
          },
          "transcription_delay_ms": {
            "description": "How long the transcription lagged the audio. Set on ``pipeline_eou``\nrows.",
            "type": "integer"
          },
          "ttft_ms": {
            "description": "Time to the provider's first generated token. The closest measure of\nhow long the agent took to react. Set on ``realtime_model`` rows.",
            "type": "integer"
          }
        },
        "required": [
          "at",
          "source"
        ],
        "title": "SessionTurn",
        "type": "object"
      },
      "SessionUsage": {
        "description": "Usage summary for one session, in provider-reported units.\n\n``duration_seconds`` is set once the session ends. The rest of the\ndetail arrives with the summary, so it is present only while\n``usage_status`` is ``RECORDED``, at which point the numbers are\nfinal. ``tokens`` is absent when the provider reports none.",
        "properties": {
          "agent_speaking_seconds": {
            "description": "How long the agent was speaking.",
            "type": "number"
          },
          "duration_seconds": {
            "description": "Wall-clock length of the session, set once it ends.",
            "type": "number"
          },
          "model": {
            "description": "The concrete model id that ran, which a family alias resolves to.",
            "type": "string"
          },
          "provider": {
            "description": "Which model provider actually ran the session, after the server\nresolved the requested model.",
            "enum": [
              "gemini",
              "openai",
              "openai_mini",
              "openai_live",
              "grok",
              "cosmo_voice_personaplex",
              "cosmo_voice_ultravox"
            ],
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/SessionStatus",
            "description": "Where the session itself ended up."
          },
          "tokens": {
            "$ref": "#/components/schemas/SessionTokenUsage",
            "description": "The token breakdown. Absent when the provider reported none \u2014 that is\nabsence of reporting, not zero usage."
          },
          "turn_count": {
            "description": "How many turns the session took.",
            "type": "integer"
          },
          "usage_status": {
            "$ref": "#/components/schemas/UsageStatus",
            "description": "Whether the usage summary exists yet. Poll while this is ``pending``;\nstop on ``unavailable``."
          },
          "user_speaking_seconds": {
            "description": "How long the user was speaking.",
            "type": "number"
          }
        },
        "required": [
          "status",
          "usage_status"
        ],
        "title": "SessionUsage",
        "type": "object"
      },
      "SilenceTimeout": {
        "additionalProperties": false,
        "description": "Server-runtime hook: perform `action` after `timeout_seconds` of user\nsilence.",
        "properties": {
          "action": {
            "description": "What to do when the timeout fires \u2014 speak a line, or end the call.",
            "discriminator": {
              "mapping": {
                "end_call": "#/components/schemas/EndCall",
                "say": "#/components/schemas/Say"
              },
              "propertyName": "type"
            },
            "oneOf": [
              {
                "$ref": "#/components/schemas/Say"
              },
              {
                "$ref": "#/components/schemas/EndCall"
              }
            ]
          },
          "max_count": {
            "default": 3,
            "description": "How many times this hook may fire, 1\u201310, so a silent caller is not\nprompted forever. Counted per run of silence when ``reset_mode`` is\n``on_user_speech``, and across the session when it is ``never``.",
            "maximum": 10.0,
            "minimum": 1.0,
            "type": "integer"
          },
          "name": {
            "description": "Label for this hook, for your own reference and the server's logs. It\nis not carried on the event the hook fires, so a session running several\nsilence hooks cannot tell from the event which one fired.",
            "maxLength": 256,
            "type": "string"
          },
          "present_multiplier": {
            "description": "How much to widen ``timeout_seconds`` once the caller has spoken at\nleast once, 1\u201310, so a present but quiet caller waits longer than a line\nthat was silent from the start. ``1`` waits the same either way. Omit to\nuse the server's default.",
            "maximum": 10.0,
            "minimum": 1.0,
            "type": "number"
          },
          "reset_mode": {
            "default": "never",
            "description": "Whether the fire count resets. ``never`` counts across the whole\nsession; ``on_user_speech`` starts over each time the user speaks.",
            "enum": [
              "never",
              "on_user_speech"
            ],
            "type": "string"
          },
          "timeout_seconds": {
            "description": "Seconds of user silence before the action runs, 1\u20131000. Scaled by\n``present_multiplier`` once the caller has spoken at least once.",
            "maximum": 1000.0,
            "minimum": 1.0,
            "type": "number"
          },
          "trigger": {
            "const": "user.speech.timeout",
            "default": "user.speech.timeout",
            "description": "What fires the hook. Always ``user.speech.timeout``.",
            "type": "string"
          }
        },
        "required": [
          "timeout_seconds",
          "action"
        ],
        "title": "SilenceTimeout",
        "type": "object"
      },
      "SpeakerLogToolSpec": {
        "additionalProperties": false,
        "description": "Opt-in to the server-kept speaker log of the room.\n\nThe server runs a diarizing transcript of the room's audio beside the\nmodel and offers a tool that reads the last few seconds of it back,\none stable label per voice (``S0``, ``S1``, \u2026); the model binds labels\nto people from what they say about themselves. Zero-config; unknown\nfields are a schema error. Availability is checked at session start: a\ndeployment that cannot run the transcript starts the session without\nit and reports the drop on ``ready.rejected_tools``.",
        "properties": {
          "kind": {
            "const": "speaker_log",
            "default": "speaker_log",
            "description": "The tool kind. Always ``speaker_log``.",
            "type": "string"
          }
        },
        "required": [
          "kind"
        ],
        "title": "SpeakerLogToolSpec",
        "type": "object"
      },
      "TavusAvatar": {
        "additionalProperties": false,
        "description": "A Tavus renderer for the agent's video avatar.",
        "properties": {
          "face_id": {
            "description": "Which Tavus face renders the agent.",
            "minLength": 1,
            "type": "string"
          },
          "provider": {
            "const": "tavus",
            "type": "string"
          }
        },
        "required": [
          "provider",
          "face_id"
        ],
        "title": "TavusAvatar",
        "type": "object"
      },
      "ThinkingLevel": {
        "description": "Reasoning depth the client may request for the Gemini realtime model.\n\nMirrors ``google.genai.types.ThinkingLevel`` (minus the unspecified\nsentinel). The server maps it onto the upstream enum; ``None`` on the init\nleaves the server default in place.",
        "enum": [
          "minimal",
          "low",
          "medium",
          "high"
        ],
        "title": "ThinkingLevel",
        "type": "string"
      },
      "ToolCallEvent": {
        "description": "Model decided to invoke a server-executed tool.\n\nThree-event lifecycle:\n  ``tool-call`` (model decided)\n    \u2192 ``tool-dispatch-started`` (server-side handler began)\n    \u2192 ``tool-result`` (completed).",
        "properties": {
          "name": {
            "description": "Name of the tool being invoked.",
            "type": "string"
          },
          "tool_call_id": {
            "description": "Stable per-invocation id (the upstream's function-call id). Use this\nto correlate the ``tool-call`` / ``tool-dispatch-started`` /\n``tool-result`` triple for one invocation. Distinct from the\nmessage-level ``id``.",
            "type": "string"
          },
          "type": {
            "const": "tool-call",
            "default": "tool-call",
            "description": "The event type. Always ``tool-call``.",
            "type": "string"
          }
        },
        "required": [
          "tool_call_id",
          "name",
          "type"
        ],
        "title": "ToolCallEvent",
        "type": "object"
      },
      "ToolDispatchStartedEvent": {
        "description": "Server-side handler for the tool call began executing.\n\nSits between ``tool-call`` and ``tool-result`` to let UIs show a\n\"dispatching\u2026\" state \u2014 useful for slow tools where the gap between\nmodel decision and result is multiple seconds.",
        "properties": {
          "name": {
            "description": "Name of the tool that started executing.",
            "type": "string"
          },
          "tool_call_id": {
            "description": "Correlates with the ``tool-call`` that opened this invocation.",
            "type": "string"
          },
          "type": {
            "const": "tool-dispatch-started",
            "default": "tool-dispatch-started",
            "description": "The event type. Always ``tool-dispatch-started``.",
            "type": "string"
          }
        },
        "required": [
          "tool_call_id",
          "name",
          "type"
        ],
        "title": "ToolDispatchStartedEvent",
        "type": "object"
      },
      "ToolInvocationEvent": {
        "description": "Server asks the connected client to run a tool locally.\n\nSent only for tools declared via ``kind=\"client\"`` specs at session\nstart. The SDK runs the tool against the client's local resources and\nreturns the result out of band; the server validates it before\nresolving the pending dispatch.\n\n``request_id`` is the dispatch correlation id; ``tool_call_id`` is the\nupstream's identifier for the underlying function call and is kept\ndistinct so observability messages (``tool-call`` / ``tool-result``)\nstill correlate by the upstream's id.\n\n``origin`` identifies which producer authored the invocation:\n``\"realtime\"`` for the voice model (default), ``\"server\"`` for a\nserver tool runtime reaching back through the same bridge. Clients\nthat don't render the distinction can ignore the field.\n\n``executable`` is ``False`` for an informational mirror whose execution\nruns out-of-band; ``True`` (default, and for a missing field) means the\nrecipient runs the tool.",
        "properties": {
          "args": {
            "additionalProperties": true,
            "description": "Arguments the model produced, matching the tool's declared schema.",
            "type": "object"
          },
          "executable": {
            "default": true,
            "description": "Whether the recipient should run the tool. ``False`` marks an\ninformational mirror of a call executed elsewhere.",
            "type": "boolean"
          },
          "name": {
            "description": "Name of the client tool to run.",
            "type": "string"
          },
          "origin": {
            "default": "realtime",
            "description": "Who asked: ``realtime`` for the live model, ``server`` for a\nserver-side runtime reaching the same tool.",
            "enum": [
              "realtime",
              "server"
            ],
            "type": "string"
          },
          "request_id": {
            "description": "Identifies this notification. Minted fresh for the event and\nunrelated to the call's own transport-level request, so it is not a\nhandle to reply on \u2014 client tools are invoked and answered over the\ntransport's RPC channel, and this event only mirrors that.",
            "type": "string"
          },
          "tool_call_id": {
            "description": "The model's own id for the call, shared with the ``tool-call``\nlifecycle.",
            "type": "string"
          },
          "type": {
            "const": "tool-invocation",
            "default": "tool-invocation",
            "description": "The event type. Always ``tool-invocation``.",
            "type": "string"
          }
        },
        "required": [
          "request_id",
          "tool_call_id",
          "name",
          "type"
        ],
        "title": "ToolInvocationEvent",
        "type": "object"
      },
      "ToolJobResult": {
        "additionalProperties": false,
        "description": "A long-running client tool finished off-band and is delivering its\nterminal result. The server resolves the original tool call from\n``job_id`` and injects the outcome; ``summary`` / ``error`` are the\nmodel-facing text and ``result`` is structured data for logging.\n\nHalf of the background client-tool primitive \u2014 a tool whose work outlives\nthe RPC reply budget acks first and lands its outcome here. Every SDK\nimplements it, so it is protocol vocabulary rather than a first-party\nextension.",
        "properties": {
          "error": {
            "description": "Model-facing text for a failed job \u2014 what the assistant is told went\nwrong.",
            "type": "string"
          },
          "job_id": {
            "description": "Identifies the job this result belongs to \u2014 the id the acked tool call\nwas given.",
            "type": "string"
          },
          "result": {
            "additionalProperties": true,
            "description": "Structured outcome. Accepted for forward compatibility but not\nconsumed today \u2014 only ``status`` and ``summary``/``error`` reach the\nmodel, so put anything the agent must act on in those.",
            "type": "object"
          },
          "status": {
            "description": "Whether the work succeeded. ``failed`` pairs with ``error``.",
            "enum": [
              "completed",
              "failed"
            ],
            "type": "string"
          },
          "summary": {
            "description": "Model-facing text for a completed job \u2014 what the assistant is told\ncame back.",
            "type": "string"
          },
          "tool_name": {
            "description": "Name of the tool that ran, for logging and attribution.",
            "type": "string"
          },
          "type": {
            "const": "tool_job_result",
            "default": "tool_job_result",
            "description": "The message type. Always ``tool_job_result``.",
            "type": "string"
          }
        },
        "required": [
          "job_id",
          "tool_name",
          "status",
          "type"
        ],
        "title": "ToolJobResult",
        "type": "object"
      },
      "ToolResultEvent": {
        "description": "Server-side tool finished. ``summary`` is a short human-readable line.",
        "properties": {
          "ok": {
            "description": "Whether the tool succeeded.",
            "type": "boolean"
          },
          "summary": {
            "description": "Short human-readable line about the outcome, for display.",
            "type": "string"
          },
          "tool_call_id": {
            "description": "Correlates with the ``tool-call`` that opened this invocation.",
            "type": "string"
          },
          "type": {
            "const": "tool-result",
            "default": "tool-result",
            "description": "The event type. Always ``tool-result``.",
            "type": "string"
          }
        },
        "required": [
          "tool_call_id",
          "ok",
          "type"
        ],
        "title": "ToolResultEvent",
        "type": "object"
      },
      "TranscriptDeltaEvent": {
        "description": "Streaming transcript event for either speaker.\n\nFor the assistant role in audio sessions, this is the audio\ntranscription \u2014 the words the listener actually heard. The model may\nALSO emit a separate text channel via ``ModelTextEvent``; consumers\nthat render a \"what was spoken\" bubble should subscribe to\n``transcript`` only.\n\nContract (wire-level, applies to both user and assistant):\n\n* Streaming events (``is_final=False``): ``text`` is the **new\n  fragment since the previous event** for that role's turn (a\n  delta). Consumers append.\n* Terminating event (``is_final=True``): ``text`` is the\n  **cumulative full transcript** for the turn. Consumers replace\n  whatever they accumulated with this value.\n\nTwo carve-outs where the terminating event is NOT the full turn, both\nload-bearing rather than accidental:\n\n* **Empty final.** A turn the model produced nothing usable for (a\n  suppressed or garbled turn) finalizes with ``text=\"\"``. It means an\n  empty turn, not \"unchanged\" \u2014 a consumer that skips empty finals\n  leaves the turn's bubble dangling into the next one.\n* **Suffix final on a silent session.** When the agent runs with\n  ``audio.output=False``, a user final that arrives after the VAD\n  endpoint already committed the utterance is stripped of the committed\n  prefix, so it carries only the remainder. Replacing on final would\n  drop the part committed at the endpoint.\n\nA consumer that only ever appends non-finals and replaces on final is\ncorrect for the common path; these two need the extra handling.",
        "properties": {
          "is_final": {
            "description": "Whether this closes the turn. Append while it is false; on true,\nreplace what you accumulated \u2014 except in the two cases the class\ndocstring describes, where the final carries less than the whole turn\nand replacing loses text.",
            "type": "boolean"
          },
          "role": {
            "$ref": "#/components/schemas/TranscriptRole",
            "description": "Who was speaking \u2014 the user, or the assistant."
          },
          "text": {
            "description": "The new fragment while ``is_final`` is false, the whole turn once it\nis true. The class docstring names the two cases where a final carries\nless than the whole turn.",
            "type": "string"
          },
          "type": {
            "const": "transcript",
            "default": "transcript",
            "description": "The event type. Always ``transcript``.",
            "type": "string"
          }
        },
        "required": [
          "role",
          "text",
          "is_final",
          "type"
        ],
        "title": "TranscriptDeltaEvent",
        "type": "object"
      },
      "TranscriptRole": {
        "description": "Speaker for a transcript fragment.",
        "enum": [
          "USER",
          "ASSISTANT"
        ],
        "title": "TranscriptRole",
        "type": "string"
      },
      "TtsProvider": {
        "description": "External TTS providers a speech-to-speech model can render.",
        "enum": [
          "elevenlabs",
          "cartesia"
        ],
        "title": "TtsProvider",
        "type": "string"
      },
      "TtsVoice": {
        "description": "Provider-agnostic external-TTS voice selection. The runtime maps this to\nthe speech-to-speech provider's own wire format. ``voice_id`` is the TTS\nprovider's own voice id; the tuning fields apply to ElevenLabs (Cartesia\nignores the ElevenLabs-specific ones).",
        "properties": {
          "model": {
            "description": "Concrete TTS model to synthesize with. Unset runs the provider\ndefault.",
            "maxLength": 128,
            "type": "string"
          },
          "provider": {
            "$ref": "#/components/schemas/TtsProvider",
            "description": "Which TTS provider synthesizes the voice."
          },
          "similarity_boost": {
            "description": "ElevenLabs only, ``0``\u2013``1``: how closely to match the reference\nvoice.",
            "maximum": 1.0,
            "minimum": 0.0,
            "type": "number"
          },
          "speed": {
            "description": "Playback rate multiplier. Unset keeps the provider default.",
            "type": "number"
          },
          "stability": {
            "description": "ElevenLabs only, ``0``\u2013``1``: lower varies delivery more between\ngenerations, higher keeps it consistent.",
            "maximum": 1.0,
            "minimum": 0.0,
            "type": "number"
          },
          "style": {
            "description": "ElevenLabs only, ``0``\u2013``1``: how much stylistic exaggeration to\napply.",
            "maximum": 1.0,
            "minimum": 0.0,
            "type": "number"
          },
          "use_speaker_boost": {
            "description": "ElevenLabs only: boost similarity to the reference speaker, at some\nlatency cost.",
            "type": "boolean"
          },
          "voice_id": {
            "description": "The provider's own voice id.",
            "maxLength": 256,
            "type": "string"
          }
        },
        "required": [
          "provider",
          "voice_id"
        ],
        "title": "TtsVoice",
        "type": "object"
      },
      "TurnCompleteEvent": {
        "description": "Marks the end of a turn so the client can finalize a transcript bubble.",
        "properties": {
          "role": {
            "$ref": "#/components/schemas/TranscriptRole",
            "description": "Whose turn ended."
          },
          "type": {
            "const": "turn-complete",
            "default": "turn-complete",
            "description": "The event type. Always ``turn-complete``.",
            "type": "string"
          }
        },
        "required": [
          "role",
          "type"
        ],
        "title": "TurnCompleteEvent",
        "type": "object"
      },
      "TurnDetectionMode": {
        "description": "Which turn detector ends the user's turn.\n\n``server_vad`` ends the turn on a fixed silence window;\n``semantic_vad`` runs OpenAI's classifier that ends it as soon as the\nutterance reads as complete (OpenAI-only); ``cosmo_vad`` runs Cosmo's own\nsemantic detector in the realtime worker (Gemini-only). ``None`` keeps\nthe provider default: ``server_vad`` on OpenAI and Grok, ``cosmo_vad``\non Gemini.",
        "enum": [
          "server_vad",
          "semantic_vad",
          "cosmo_vad"
        ],
        "title": "TurnDetectionMode",
        "type": "string"
      },
      "UsageEvent": {
        "description": "First-party cosmo event: cumulative token usage for the live session,\nsplit by direction and modality. Emitted for any external session whose\nupstream reports usage \u2014 translation is stateless, not gated on the\n``cosmo`` config block.",
        "properties": {
          "input_audio_tokens": {
            "default": 0,
            "description": "Audio tokens sent to the model so far this session.",
            "type": "integer"
          },
          "input_cached_tokens": {
            "default": 0,
            "description": "Input tokens served from the provider's cache, already counted in the\ninput totals above.",
            "type": "integer"
          },
          "input_image_tokens": {
            "default": 0,
            "description": "Image tokens sent to the model so far this session.",
            "type": "integer"
          },
          "input_text_tokens": {
            "default": 0,
            "description": "Text tokens sent to the model so far this session.",
            "type": "integer"
          },
          "output_audio_tokens": {
            "default": 0,
            "description": "Audio tokens the model produced so far this session.",
            "type": "integer"
          },
          "output_text_tokens": {
            "default": 0,
            "description": "Text tokens the model produced so far this session.",
            "type": "integer"
          },
          "total_tokens": {
            "default": 0,
            "description": "Every token counted above, as the provider reports the total.",
            "type": "integer"
          },
          "type": {
            "const": "cosmo.usage",
            "default": "cosmo.usage",
            "description": "The event type. Always ``cosmo.usage``.",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "title": "UsageEvent",
        "type": "object"
      },
      "UsageStatus": {
        "description": "Whether a session's detailed usage summary is available.\n\n``PENDING`` while the session runs and for a short window after it\nends, before the summary is written. ``RECORDED`` once it is there\nand the numbers are final. ``UNAVAILABLE`` once that window has\npassed without one arriving: a session with no turn or speech\nactivity records none, and neither does one torn down abnormally.",
        "enum": [
          "pending",
          "recorded",
          "unavailable"
        ],
        "title": "UsageStatus",
        "type": "string"
      },
      "UserSpeechTimeoutEvent": {
        "description": "A server-runtime silence timeout fired: the user was silent past a\nconfigured threshold and the server performed `action`. Observability only.",
        "properties": {
          "action": {
            "description": "What the server did in response.",
            "discriminator": {
              "mapping": {
                "end_call": "#/components/schemas/EndCall",
                "say": "#/components/schemas/Say"
              },
              "propertyName": "type"
            },
            "oneOf": [
              {
                "$ref": "#/components/schemas/Say"
              },
              {
                "$ref": "#/components/schemas/EndCall"
              }
            ]
          },
          "max_count": {
            "description": "The hook's nudge ceiling. It goes quiet after the last one rather\nthan escalating; under ``reset_mode: on_user_speech`` the count resets on\nuser speech, so this bounds one run of silence, not the session.",
            "type": "integer"
          },
          "session_id": {
            "description": "Session the timeout fired on.",
            "type": "string"
          },
          "silence_ms": {
            "description": "Silence accrued in the window that fired. The clock restarts after\neach firing, so on a second or later nudge this measures from the\nprevious one, not from the last time the user spoke.",
            "type": "integer"
          },
          "trigger_count": {
            "description": "Which firing this is in the current run, from one. Under\n``reset_mode: on_user_speech`` the count restarts when the user speaks,\nso it can return to one within a session.",
            "type": "integer"
          },
          "type": {
            "const": "user-speech-timeout",
            "default": "user-speech-timeout",
            "description": "The event type. Always ``user-speech-timeout``.",
            "type": "string"
          }
        },
        "required": [
          "session_id",
          "silence_ms",
          "trigger_count",
          "max_count",
          "action",
          "type"
        ],
        "title": "UserSpeechTimeoutEvent",
        "type": "object"
      },
      "UserStartedSpeakingEvent": {
        "description": "Server-side VAD detected user voice activity start.\n\nInformation-only \u2014 clients use this for UI indicators (mic-level\nhighlight, \"listening\u2026\" label). Does NOT change client mic behavior;\nthe mic keeps streaming continuously so overlapping speech is captured.",
        "properties": {
          "type": {
            "const": "user-started-speaking",
            "default": "user-started-speaking",
            "description": "The event type. Always ``user-started-speaking``.",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "title": "UserStartedSpeakingEvent",
        "type": "object"
      },
      "UserStoppedSpeakingEvent": {
        "description": "Server-side VAD detected the end of user voice activity.\n\nInformation-only, the counterpart to ``user-started-speaking``: it ends\nthe \"listening\u2026\" indicator. It does not mean the turn is over \u2014 the\nturn detector decides that separately.",
        "properties": {
          "type": {
            "const": "user-stopped-speaking",
            "default": "user-stopped-speaking",
            "description": "The event type. Always ``user-stopped-speaking``.",
            "type": "string"
          }
        },
        "required": [
          "type"
        ],
        "title": "UserStoppedSpeakingEvent",
        "type": "object"
      },
      "VerifyResponse": {
        "description": "Result of a credential preflight \u2014 a 200 means the credential\nauthenticated; the fields say what it can do from here.",
        "properties": {
          "can_start_sessions": {
            "description": "Whether this credential carries the scope a session start needs. False means the credential is valid but under-scoped.",
            "type": "boolean"
          },
          "credential": {
            "$ref": "#/components/schemas/CredentialKind",
            "description": "Which of the two credential kinds the server saw."
          },
          "external_user_id": {
            "description": "The end user a minted token is bound to; null for an API key.",
            "type": "string"
          },
          "realtime_voice_available": {
            "description": "Whether this deployment has the default voice stack configured \u2014 LiveKit plus the default provider's key. A floor, not a per-session guarantee: a session that requests an opt-in provider is checked against that provider instead, so a start can still return 503 when this is true.",
            "type": "boolean"
          },
          "scopes": {
            "description": "Scopes granted to this credential, e.g. ``realtime:start``. Reported after hierarchy expansion: a credential holding the deprecated ``realtime:use`` umbrella lists it alongside the child scopes it implies.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "workspace": {
            "$ref": "#/components/schemas/WorkspaceInfo",
            "description": "The workspace the credential is bound to. Present for an API key, which the workspace's own developer holds; null for a minted token, which is held by an end user."
          }
        },
        "required": [
          "credential",
          "scopes",
          "can_start_sessions",
          "realtime_voice_available"
        ],
        "title": "VerifyResponse",
        "type": "object"
      },
      "VoiceConfig": {
        "additionalProperties": false,
        "description": "How the agent sounds: the prebuilt voice and the per-run speaking\nstyle. One sub-object shared by both agent variants.",
        "properties": {
          "name": {
            "description": "Provider-specific prebuilt voice id. When ``None`` the upstream picks\nper session \u2014 the voice then drifts between connects. Clients that want\na stable voice send one explicitly.",
            "maxLength": 128,
            "type": "string"
          },
          "speaking_style": {
            "description": "Caller-supplied \"how to speak\" instruction text, appended to the system\nprompt as its own section after the persona. Resolved client-side (the SDK\ncalls a callback \u2192 string) before this is sent. ``None`` = none.",
            "maxLength": 8192,
            "type": "string"
          }
        },
        "title": "VoiceConfig",
        "type": "object"
      },
      "WebSearchToolSpec": {
        "additionalProperties": false,
        "description": "Opt-in to the server-executed web-search tool, as its own typed kind.\n\nThe server owns the model-facing declaration (name, description, query\nschema) \u2014 the client only opts in, so the spec carries no fields today.\nFuture configuration (result count, domain filters, freshness) lands as\ntyped fields here. Unknown fields are a schema error.\n\nTyped per-tool kinds supersede the generic ``kind=\"server\"``\nname-reference, which is deprecated and rejected at session start.\nAvailability is checked at session start: a\ndeployment or workspace that cannot run the tool starts the session\nwithout it and reports the drop on ``ready.rejected_tools``.",
        "properties": {
          "kind": {
            "const": "web_search",
            "default": "web_search",
            "description": "The tool kind. Always ``web_search``.",
            "type": "string"
          }
        },
        "required": [
          "kind"
        ],
        "title": "WebSearchToolSpec",
        "type": "object"
      },
      "WorkspaceInfo": {
        "description": "The workspace the credential is bound to.",
        "properties": {
          "name": {
            "description": "Human-readable workspace name.",
            "type": "string"
          },
          "slug": {
            "description": "URL-safe workspace identifier.",
            "type": "string"
          }
        },
        "required": [
          "name",
          "slug"
        ],
        "title": "WorkspaceInfo",
        "type": "object"
      }
    },
    "securitySchemes": {
      "ApiKeyAuth": {
        "bearerFormat": "CosmoRealtime API key",
        "description": "Workspace-scoped API key. Pass as ``Authorization: Bearer <key>``. Required scopes are declared per-operation in ``x-required-scopes``.",
        "scheme": "bearer",
        "type": "http"
      },
      "UserTokenAuth": {
        "bearerFormat": "User access token",
        "description": "Acts-as-user access token. Pass as ``Authorization: Bearer <token>``. API keys are rejected on operations that require this scheme; the required capability is declared per-operation in ``x-required-capability``.",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "info": {
    "contact": {
      "name": "CosmoRealtime API Support"
    },
    "description": "Public CosmoRealtime API. Authenticate with a workspace-scoped API key carrying the ``realtime:*`` scopes its operations require (``x-required-scopes`` on each operation); the SDK handles LiveKit room join and the JSON control-plane wire protocol on top.",
    "summary": "Realtime voice and multimodal sessions over WebSockets and LiveKit.",
    "title": "CosmoRealtime API",
    "version": "0.1.0"
  },
  "openapi": "3.1.0",
  "paths": {
    "/api/v1/external/auth/token": {
      "post": {
        "description": "Mint a project-scoped JWT for one of the developer's end users.\n\nThe end user is provisioned as an external user that OWNS its project, so\nevery resource the resulting JWT creates attributes to the end user \u2014 not to\nthe developer (the API key owner, ``ctx.user_id``).",
        "operationId": "mint_project_token",
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "mint": {
                  "summary": "Mint a one-hour token for an end user",
                  "value": {
                    "external_user_id": "user-8421@example.com",
                    "ttl_seconds": 3600
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/MintTokenRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MintTokenResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "detail": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "detail"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Missing or invalid API key. Rejected by the authentication layer, so the body is ``{\"detail\": \"...\"}`` \u2014 not the error envelope."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Rejected \u2014 see ``error.code`` (typed rejections) and ``error.errors`` (field-level validation). Full rejection-code table: [Session start errors](https://platform.askcosmo.ai/docs/reference/api/session-start#errors)."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Mint an end-user token",
        "tags": [
          "external-auth"
        ],
        "x-auth-type": "external_api",
        "x-required-scopes": [
          "user_tokens:mint"
        ],
        "x-workspace-exempt": true
      }
    },
    "/api/v1/external/auth/token/{token_id}": {
      "delete": {
        "description": "Revoke one minted end-user token before it expires.\n\n``token_id`` is the id the mint response returned. Revocation takes\neffect on the next request presenting the JWT; it does not end sessions\nalready running. Idempotent \u2014 revoking an already-revoked token succeeds.",
        "operationId": "revoke_project_token",
        "parameters": [
          {
            "in": "path",
            "name": "token_id",
            "required": true,
            "schema": {
              "title": "Token Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RevokeTokenResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "detail": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "detail"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Missing or invalid API key. Rejected by the authentication layer, so the body is ``{\"detail\": \"...\"}`` \u2014 not the error envelope."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Rejected \u2014 see ``error.code`` (typed rejections) and ``error.errors`` (field-level validation). Full rejection-code table: [Session start errors](https://platform.askcosmo.ai/docs/reference/api/session-start#errors)."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Revoke an end-user token",
        "tags": [
          "external-auth"
        ],
        "x-auth-type": "external_api",
        "x-required-scopes": [
          "user_tokens:mint"
        ],
        "x-workspace-exempt": true
      }
    },
    "/api/v1/external/realtime/session/start": {
      "post": {
        "description": "Start a session from a ``session-config`` body and return the LiveKit\njoin credentials. A saved agent is launched by reference\n(``agent: {type: \"catalog\", name}``) and the session is attributed to it\nautomatically; an ``inline`` agent is defined in the request itself.",
        "operationId": "start_realtime_session",
        "requestBody": {
          "content": {
            "application/json": {
              "examples": {
                "catalog_agent": {
                  "summary": "Launch a saved catalog agent",
                  "value": {
                    "agent": {
                      "name": "support-agent",
                      "type": "catalog"
                    },
                    "sdk": {
                      "name": "cosmo-ai-sdk",
                      "version": "0.5.0"
                    },
                    "type": "session-config"
                  }
                },
                "inline_agent": {
                  "summary": "Define the agent inline",
                  "value": {
                    "agent": {
                      "instructions": "You are a concise support agent.",
                      "type": "inline",
                      "voice": {
                        "name": "Puck"
                      }
                    },
                    "sdk": {
                      "name": "cosmo-ai-sdk",
                      "version": "0.4.0"
                    },
                    "session": {
                      "max_session_seconds": 1800
                    },
                    "type": "session-config"
                  }
                }
              },
              "schema": {
                "$ref": "#/components/schemas/SessionConfig"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "detail": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "detail"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Missing or invalid API key. Rejected by the authentication layer, so the body is ``{\"detail\": \"...\"}`` \u2014 not the error envelope."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Rejected \u2014 see ``error.code`` (typed rejections) and ``error.errors`` (field-level validation). Full rejection-code table: [Session start errors](https://platform.askcosmo.ai/docs/reference/api/session-start#errors)."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Start a realtime session",
        "tags": [
          "external-realtime"
        ],
        "x-auth-type": "external_api",
        "x-required-scopes": [
          "realtime:start"
        ],
        "x-workspace-exempt": true
      }
    },
    "/api/v1/external/realtime/session/{session_id}/dial": {
      "post": {
        "description": "Place an outbound phone call into a running realtime session.\n\nThe dialed party joins the session's LiveKit room as a SIP\nparticipant and the agent \u2014 already in the room \u2014 converses with\nthem. Requires an API key carrying ``realtime:dial`` plus the\nworkspace's phone-calls setting enabled, and is bound by its weekly\nper-user minute limit. A minted end-user token is rejected (403\n``dial_requires_api_key``); an unknown or ended session is rejected\n(404 / 409).",
        "operationId": "dial_realtime_session",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DialBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DialResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "detail": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "detail"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Missing or invalid API key. Rejected by the authentication layer, so the body is ``{\"detail\": \"...\"}`` \u2014 not the error envelope."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Rejected \u2014 see ``error.code`` (typed rejections) and ``error.errors`` (field-level validation). Full rejection-code table: [Session start errors](https://platform.askcosmo.ai/docs/reference/api/session-start#errors)."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Dial out from a session",
        "tags": [
          "external-realtime"
        ],
        "x-auth-type": "external_api",
        "x-required-scopes": [
          "realtime:dial"
        ],
        "x-workspace-exempt": true
      }
    },
    "/api/v1/external/realtime/verify": {
      "get": {
        "description": "Verify a realtime credential without starting a session.",
        "operationId": "verify_realtime_credential",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifyResponse"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "detail": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "detail"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Missing or invalid API key. Rejected by the authentication layer, so the body is ``{\"detail\": \"...\"}`` \u2014 not the error envelope."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Verify a realtime credential",
        "tags": [
          "external-realtime"
        ],
        "x-auth-type": "external_api",
        "x-workspace-exempt": true
      }
    },
    "/api/v1/external/sessions": {
      "get": {
        "operationId": "list_sessions",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100.0,
              "minimum": 1.0,
              "title": "Limit",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "before_started_at",
            "required": false,
            "schema": {
              "title": "Before Started At",
              "type": "number"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/SessionRecord"
                  },
                  "title": "Response List Sessions",
                  "type": "array"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "detail": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "detail"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Missing or invalid API key. Rejected by the authentication layer, so the body is ``{\"detail\": \"...\"}`` \u2014 not the error envelope."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Rejected \u2014 see ``error.code`` (typed rejections) and ``error.errors`` (field-level validation). Full rejection-code table: [Session start errors](https://platform.askcosmo.ai/docs/reference/api/session-start#errors)."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "List sessions",
        "tags": [
          "external-sessions"
        ],
        "x-auth-type": "external_api",
        "x-required-scopes": [
          "realtime:read"
        ],
        "x-workspace-exempt": true
      }
    },
    "/api/v1/external/sessions/capabilities": {
      "get": {
        "description": "Report which realtime model providers the caller may select.\n\nA provider reported unavailable is rejected at session start if\nrequested, so clients can use this to hide options that would not work.",
        "operationId": "get_realtime_capabilities",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProviderCapabilities"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "detail": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "detail"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Missing or invalid API key. Rejected by the authentication layer, so the body is ``{\"detail\": \"...\"}`` \u2014 not the error envelope."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get provider capabilities",
        "tags": [
          "external-sessions"
        ],
        "x-auth-type": "external_api",
        "x-required-scopes": [
          "realtime:read"
        ],
        "x-workspace-exempt": true
      }
    },
    "/api/v1/external/sessions/import": {
      "post": {
        "description": "Import a recording bundle as a COMPLETED voice session.\n\nStores the transcript (and optional mono WAV) and creates a COMPLETED\nsession, so the import reads back through the same list/get/transcript\nendpoints. Requires the ``recordings:write`` capability plus\nrecordings-admin, which also grants ``realtime:read`` so the importer can\nread the session back.",
        "operationId": "import_session",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SessionImportRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionRecord"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "detail": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "detail"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Missing or invalid access token. Rejected by the authentication layer, so the body is ``{\"detail\": \"...\"}`` \u2014 not the error envelope."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Rejected \u2014 see ``error.code`` (typed rejections) and ``error.errors`` (field-level validation). Full rejection-code table: [Session start errors](https://platform.askcosmo.ai/docs/reference/api/session-start#errors)."
          }
        },
        "security": [
          {
            "UserTokenAuth": []
          }
        ],
        "summary": "Import a session",
        "tags": [
          "external-sessions"
        ],
        "x-auth-type": "external_user",
        "x-required-capability": "recordings:write",
        "x-workspace-exempt": true
      }
    },
    "/api/v1/external/sessions/{session_id}": {
      "delete": {
        "operationId": "delete_session",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "detail": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "detail"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Missing or invalid API key. Rejected by the authentication layer, so the body is ``{\"detail\": \"...\"}`` \u2014 not the error envelope."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Rejected \u2014 see ``error.code`` (typed rejections) and ``error.errors`` (field-level validation). Full rejection-code table: [Session start errors](https://platform.askcosmo.ai/docs/reference/api/session-start#errors)."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Delete a session",
        "tags": [
          "external-sessions"
        ],
        "x-auth-type": "external_api",
        "x-required-scopes": [
          "realtime:delete"
        ],
        "x-workspace-exempt": true
      },
      "get": {
        "operationId": "get_session",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionRecord"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "detail": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "detail"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Missing or invalid API key. Rejected by the authentication layer, so the body is ``{\"detail\": \"...\"}`` \u2014 not the error envelope."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Rejected \u2014 see ``error.code`` (typed rejections) and ``error.errors`` (field-level validation). Full rejection-code table: [Session start errors](https://platform.askcosmo.ai/docs/reference/api/session-start#errors)."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get a session",
        "tags": [
          "external-sessions"
        ],
        "x-auth-type": "external_api",
        "x-required-scopes": [
          "realtime:read"
        ],
        "x-workspace-exempt": true
      }
    },
    "/api/v1/external/sessions/{session_id}/artifacts": {
      "get": {
        "description": "Signed download URLs for one session's composed recordings.\n\nA session that is still running, or that recorded nothing, returns an\nempty list rather than a 404 \u2014 the session exists, its recordings do\nnot yet.",
        "operationId": "get_session_artifacts",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionArtifacts"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "detail": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "detail"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Missing or invalid API key. Rejected by the authentication layer, so the body is ``{\"detail\": \"...\"}`` \u2014 not the error envelope."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Rejected \u2014 see ``error.code`` (typed rejections) and ``error.errors`` (field-level validation). Full rejection-code table: [Session start errors](https://platform.askcosmo.ai/docs/reference/api/session-start#errors)."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get a session's recordings",
        "tags": [
          "external-sessions"
        ],
        "x-auth-type": "external_api",
        "x-required-scopes": [
          "realtime:read"
        ],
        "x-workspace-exempt": true
      }
    },
    "/api/v1/external/sessions/{session_id}/timeline": {
      "get": {
        "description": "When each turn happened and how long its parts took.",
        "operationId": "get_session_timeline",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionTimeline"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "detail": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "detail"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Missing or invalid API key. Rejected by the authentication layer, so the body is ``{\"detail\": \"...\"}`` \u2014 not the error envelope."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Rejected \u2014 see ``error.code`` (typed rejections) and ``error.errors`` (field-level validation). Full rejection-code table: [Session start errors](https://platform.askcosmo.ai/docs/reference/api/session-start#errors)."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get a session's turn timeline",
        "tags": [
          "external-sessions"
        ],
        "x-auth-type": "external_api",
        "x-required-scopes": [
          "realtime:logs"
        ],
        "x-workspace-exempt": true
      }
    },
    "/api/v1/external/sessions/{session_id}/transcript": {
      "get": {
        "operationId": "get_session_transcript",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/SessionTranscriptTurn"
                  },
                  "title": "Response Get Session Transcript",
                  "type": "array"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "detail": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "detail"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Missing or invalid API key. Rejected by the authentication layer, so the body is ``{\"detail\": \"...\"}`` \u2014 not the error envelope."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Rejected \u2014 see ``error.code`` (typed rejections) and ``error.errors`` (field-level validation). Full rejection-code table: [Session start errors](https://platform.askcosmo.ai/docs/reference/api/session-start#errors)."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get a session transcript",
        "tags": [
          "external-sessions"
        ],
        "x-auth-type": "external_api",
        "x-required-scopes": [
          "realtime:read"
        ],
        "x-workspace-exempt": true
      }
    },
    "/api/v1/external/sessions/{session_id}/usage": {
      "get": {
        "description": "Usage summary for one session: duration, talk time, and token counts.",
        "operationId": "get_session_usage",
        "parameters": [
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Session Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionUsage"
                }
              }
            },
            "description": "Successful Response"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "detail": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "detail"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Missing or invalid API key. Rejected by the authentication layer, so the body is ``{\"detail\": \"...\"}`` \u2014 not the error envelope."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            },
            "description": "Rejected \u2014 see ``error.code`` (typed rejections) and ``error.errors`` (field-level validation). Full rejection-code table: [Session start errors](https://platform.askcosmo.ai/docs/reference/api/session-start#errors)."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "summary": "Get a session's usage",
        "tags": [
          "external-sessions"
        ],
        "x-auth-type": "external_api",
        "x-required-scopes-any-of": [
          "realtime:start",
          "realtime:read"
        ],
        "x-workspace-exempt": true
      }
    }
  },
  "servers": [
    {
      "description": "Configure with the API host for your deployment.",
      "url": "https://{host}",
      "variables": {
        "host": {
          "default": "api.example.com",
          "description": "API host for your deployment."
        }
      }
    }
  ],
  "tags": [
    {
      "description": "Start realtime sessions, dial outbound calls, and verify credentials.",
      "name": "external-realtime",
      "x-displayName": "Realtime"
    },
    {
      "description": "Read back and manage recorded sessions.",
      "name": "external-sessions",
      "x-displayName": "Voice sessions"
    },
    {
      "description": "Mint and revoke project-scoped end-user tokens.",
      "name": "external-auth",
      "x-displayName": "Auth"
    }
  ]
}
