{
  "components": {
    "schemas": {
      "Agent": {
        "properties": {
          "audio_stream_url": {
            "type": "string"
          },
          "begin_message": {
            "type": "string"
          },
          "follow_caller": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AgentFollowCaller"
              }
            ],
            "description": "Answer each caller in whichever selected language they speak. Accepted at creation so a multilingual agent can be created in one request; enabling it requires at least two languages and is refused with 400 otherwise. Creation stores the opt-in only -- the first PATCH /v1/agents/{agent_id}/voice-stack publication is what proves one shared transcriber can listen for the selected set, and refuses the publication when none can. Changing it afterwards is a voice-stack save, not an agent update. An engine without a language axis (see voice_frontend) REFUSES this field with 400 rather than accepting and dropping it."
          },
          "language": {
            "type": "string"
          },
          "language_profiles": {
            "description": "When supplied this replaces the sparse override set; omit it to preserve overrides. Each item has locale and optional exact voice, transcriber, and greeting. Omitted values inherit the shared stack; an empty greeting removes its override.",
            "items": {
              "$ref": "#/components/schemas/AgentLanguageProfileOverride"
            },
            "maxItems": 32,
            "type": "array"
          },
          "languages": {
            "description": "The languages this agent may answer in. An engine without a language axis (see voice_frontend and the agent-catalog conversation_engines block) REFUSES this field with 400 rather than accepting and dropping it.",
            "items": {
              "maxLength": 35,
              "minLength": 2,
              "type": "string"
            },
            "maxItems": 32,
            "minItems": 1,
            "type": "array"
          },
          "mode": {
            "enum": [
              "external_audio",
              "external_text",
              "hosted"
            ],
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "opening": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AgentOpening"
              }
            ],
            "description": "Accepted at creation. Read and update the opening policy through the voice-stack route; the generic agent update route refuses this field."
          },
          "organization_id": {
            "type": "string"
          },
          "project_id": {
            "type": "string"
          },
          "recording_enabled": {
            "type": "boolean"
          },
          "system_prompt": {
            "type": "string"
          },
          "tools": {
            "items": {
              "$ref": "#/components/schemas/AgentTool"
            },
            "type": "array"
          },
          "translation": {
            "$ref": "#/components/schemas/TranslationPolicy"
          },
          "voice": {
            "oneOf": [
              {
                "description": "Legacy pinned voice identifier.",
                "type": "string"
              },
              {
                "description": "Exact selected TTS provider/model/voice inventory item.",
                "properties": {
                  "model": {
                    "type": "string"
                  },
                  "provider": {
                    "type": "string"
                  },
                  "voice_id": {
                    "description": "Provider inventory id, when the selected provider exposes voices.",
                    "type": "string"
                  }
                },
                "required": [
                  "provider",
                  "model"
                ],
                "type": "object"
              }
            ]
          },
          "webhook_url": {
            "type": "string"
          }
        },
        "required": [
          "organization_id",
          "project_id",
          "mode"
        ],
        "type": "object"
      },
      "AgentAcknowledgements": {
        "additionalProperties": false,
        "description": "Optional short acknowledgement while a slow reply is still being prepared: at most one per caller turn, spoken in the agent's own voice, model and current language on a separate outgoing track, cancelled the moment the real reply starts. Off unless enabled. Without phrases the platform's reviewed list for the call language is used; a language without one stays silent rather than switching to English.",
        "properties": {
          "delay_ms": {
            "default": 1200,
            "description": "How long the reply may take before an acknowledgement is worth speaking.",
            "maximum": 3000,
            "minimum": 600,
            "type": "integer"
          },
          "enabled": {
            "type": "boolean"
          },
          "phrases": {
            "description": "Bounded neutral phrases; digits and currency marks are rejected so a phrase cannot carry a factual, payment, legal or consent claim.",
            "items": {
              "maxLength": 40,
              "minLength": 1,
              "type": "string"
            },
            "maxItems": 6,
            "type": "array"
          }
        },
        "type": "object"
      },
      "AgentAnswerDetection": {
        "additionalProperties": false,
        "description": "What the platform does when a call the carrier reported answered turns out not to be a person: non-human audio such as a caller tune, or a voicemail box. Unlike the other tuning sections, a value you send here is STORED even when it equals the current default, and only an ABSENT `answer_detection` key means \"follow the platform default\". That is deliberate: the platform default for this setting is expected to change once the detector has been measured, and an agent that explicitly chose a value keeps it when that happens. Applied when a call starts; saving never changes an in-progress call.",
        "properties": {
          "non_human_audio": {
            "description": "`shadow` records what was heard on every answered call and changes nothing the caller experiences: the classification and its detail appear on the call, and no call is ever ended by it. `end_call` acts on the verdict: the agent stops speaking and the call is ended with `ended_reason: no_human_after_answer`, and the classification and its detail appear on the call as they do under `shadow`. `off` collects nothing, so such a call carries no classification at all. `shadow` is the platform default, so it is what an agent with no stored value gets; sending it explicitly stores it, which is how you keep it if that default later moves. This property carries no JSON Schema `default` on purpose, unlike the other tuning properties: a generated client that pre-filled one would send `shadow` on every save and pin a choice the customer never made, which is the one outcome the storage rule above exists to prevent.",
            "enum": [
              "shadow",
              "end_call",
              "off"
            ],
            "type": "string"
          },
          "voicemail": {
            "description": "What the AI agent does when a voicemail box answers an outbound call. Voicemail is judged from the first seconds of audio after the answer; the phone network does not report it. Only mailbox-specific evidence counts as proof: a record beep that follows a spoken greeting, or a mailbox instruction such as \"leave a message\", from a language whose phrase list is verified (en; English is checked on every call). The beep works in every language. The greeting before it must reach three recognised words, counted by the spaces and punctuation between them, so in Chinese, Japanese, Thai and other scripts written without spaces a short greeting is undercounted, and a beep after it only raises suspicion. Candidate phrase lists (de, es, et, fr, ru) are not yet verified: what they recognise is shown as evidence and never proves a mailbox. In any other language only the beep or an English instruction can. A fast answer, a long greeting or music only raise suspicion, and a call the platform is unsure about is treated as a person. `shadow` (watch only) records the verdict and what the platform would have done on the call in Calls, and changes nothing the caller experiences. `hang_up` ends the call without a message on proof of a mailbox. `leave_message` plays the `voicemail_messages` entry for the call's language after the beep, once per contact, and hangs up without a message when there is none for that language. `off` detects nothing. `hang_up` and `leave_message` are accepted and stored now but currently run as `shadow`: they start acting only after the detector has passed its accuracy check, and each call's evidence shows both the chosen and the effective mode. `shadow` is the platform default; like `non_human_audio`, an explicitly sent value is stored even when it equals the default, and this property carries no JSON Schema `default`. The `answer_detection` object is replaced as a whole on save, so send every key you want to keep.",
            "enum": [
              "shadow",
              "hang_up",
              "leave_message",
              "off"
            ],
            "type": "string"
          },
          "voicemail_messages": {
            "additionalProperties": {
              "maxLength": 400,
              "type": "string"
            },
            "description": "The messages left when `voicemail` is `leave_message`, one per language, keyed by language code (`{\"en\": \"...\", \"et\": \"...\"}`); a locale such as `de-DE` is stored as `de`. `leave_message` requires at least one. The message played is the one for the language the call is actually in; a call in a language with no message is ended without one, never given a message in another language. Plain text; runs of whitespace are collapsed, a blank text removes that language's message, and control characters are refused. Each message is played exactly as approved, never improvised by the AI agent, so keep it neutral: who is calling and how to call back, with no account, amount or other personal details, because a voicemail box can be heard by someone other than the person you meant to reach. Messages are kept when `voicemail` changes to another mode and for languages the agent no longer offers.",
            "maxProperties": 32,
            "propertyNames": {
              "pattern": "^[A-Za-z]{2,3}([-_][A-Za-z0-9]+)*$"
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "AgentEndpoint": {
        "properties": {
          "audio_stream_url": {
            "type": "string"
          },
          "events": {
            "description": "Subscribed event types. Omitted or empty at creation defaults to call.created, call.answered, media.started, call.ended, call.completed, call.failed, call.canceled. Change it later with PATCH /v1/agent-endpoints/{agent_endpoint_id}. Subscribing to sms.received requires sms:read; call.extraction.ready requires recordings:read. Changing any endpoint destination requires both sms:read and recordings:read, including for queued deliveries and concurrent subscription changes.",
            "items": {
              "enum": [
                "call.created",
                "call.queued",
                "call.dialing",
                "call.answered",
                "media.started",
                "call.ended",
                "call.failed",
                "call.canceled",
                "call.completed",
                "call.transcript.ready",
                "call.recording.ready",
                "call.extraction.ready",
                "call.extraction.failed",
                "call.translation.ready",
                "call.translation.failed",
                "sms.translation.ready",
                "sms.translation.failed",
                "sms.received",
                "sms.sent",
                "campaign.launched",
                "campaign.contact.completed",
                "campaign.completed",
                "sms.delivery_updated"
              ],
              "type": "string"
            },
            "type": "array"
          },
          "name": {
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "project_id": {
            "type": "string"
          },
          "webhook_url": {
            "type": "string"
          }
        },
        "required": [
          "organization_id",
          "project_id",
          "webhook_url",
          "audio_stream_url"
        ],
        "type": "object"
      },
      "AgentEndpointEventsUpdate": {
        "properties": {
          "events": {
            "description": "The complete replacement subscription list; an empty array is rejected.",
            "items": {
              "enum": [
                "call.created",
                "call.queued",
                "call.dialing",
                "call.answered",
                "media.started",
                "call.ended",
                "call.failed",
                "call.canceled",
                "call.completed",
                "call.transcript.ready",
                "call.recording.ready",
                "call.extraction.ready",
                "call.extraction.failed",
                "call.translation.ready",
                "call.translation.failed",
                "sms.translation.ready",
                "sms.translation.failed",
                "sms.received",
                "sms.sent",
                "campaign.launched",
                "campaign.contact.completed",
                "campaign.completed",
                "sms.delivery_updated"
              ],
              "type": "string"
            },
            "minItems": 1,
            "type": "array"
          }
        },
        "required": [
          "events"
        ],
        "type": "object"
      },
      "AgentFollowCaller": {
        "additionalProperties": false,
        "description": "Explicit saved and published automatic multilingual policy. Existing clients may send only enabled; omitted scope means selected. With scope all, the server resolves every language compatible with the exact stack at publication and freezes that concrete set. With selected, languages supplies the allowed subset. Clients cannot supply runtime adapter options, bindings, or rates.",
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "scope": {
            "description": "Valid only when enabled. Omit for selected compatibility. All is expanded only at explicit publication and can be unavailable when a provider candidate limit is smaller than the compatible set.",
            "enum": [
              "selected",
              "all"
            ],
            "type": "string"
          }
        },
        "required": [
          "enabled"
        ],
        "type": "object"
      },
      "AgentLanguageProfileOverride": {
        "additionalProperties": false,
        "properties": {
          "greeting": {
            "maxLength": 1000,
            "type": "string"
          },
          "locale": {
            "maxLength": 35,
            "minLength": 2,
            "type": "string"
          },
          "transcriber": {
            "additionalProperties": false,
            "properties": {
              "model": {
                "maxLength": 160,
                "minLength": 1,
                "type": "string"
              },
              "provider": {
                "maxLength": 120,
                "minLength": 1,
                "type": "string"
              }
            },
            "required": [
              "provider",
              "model"
            ],
            "type": "object"
          },
          "voice": {
            "additionalProperties": false,
            "properties": {
              "model": {
                "maxLength": 160,
                "minLength": 1,
                "type": "string"
              },
              "provider": {
                "maxLength": 120,
                "minLength": 1,
                "type": "string"
              },
              "voice_id": {
                "maxLength": 512,
                "minLength": 1,
                "type": "string"
              }
            },
            "required": [
              "provider",
              "model",
              "voice_id"
            ],
            "type": "object"
          }
        },
        "required": [
          "locale"
        ],
        "type": "object"
      },
      "AgentLanguageProfiles": {
        "description": "Safe editor projection. The immutable runtime profile and credential binding IDs are never returned. Published Follow summaries expose only the frozen allowed locale set, opt-in bit, and opening locale for the authenticated test picker.",
        "properties": {
          "languages": {
            "items": {
              "type": "string"
            },
            "maxItems": 32,
            "type": "array"
          },
          "profiles": {
            "items": {
              "$ref": "#/components/schemas/AgentLanguageProfileOverride"
            },
            "maxItems": 32,
            "type": "array"
          },
          "published_at": {
            "description": "ISO 8601 publication time. An empty string with published_version 0 is the legacy-agent sentinel: no language-profile publication exists yet.",
            "type": "string"
          },
          "published_follow_caller": {
            "$ref": "#/components/schemas/AgentFollowCaller"
          },
          "published_languages": {
            "description": "Locales from the immutable published profile set. This is empty when no publication exists.",
            "items": {
              "maxLength": 35,
              "type": "string"
            },
            "maxItems": 32,
            "type": "array"
          },
          "published_starting_language": {
            "description": "Frozen Follow opening locale for the published browser-test picker, or an empty string when Follow is not enabled.",
            "maxLength": 35,
            "type": "string"
          },
          "published_version": {
            "minimum": 0,
            "type": "integer"
          },
          "revision": {
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "languages",
          "profiles",
          "published_version",
          "published_at",
          "published_languages",
          "published_follow_caller",
          "published_starting_language",
          "revision"
        ],
        "type": "object"
      },
      "AgentModelUnresponsive": {
        "additionalProperties": false,
        "description": "How long the agent may stay silent after the caller has finished speaking before the platform ends the call. It exists because a conversation engine can stop answering without saying so: a provider outage mid-call leaves the line up, the caller talking to nobody and the call billing, and on some engines nothing at all is reported. It counts only time the line was free: the clock starts when a caller turn ends, it does NOT run while the caller is speaking (their own speech is subtracted, so a caller who keeps talking is never cut off mid-sentence, and one who pauses between attempts still reaches the bound), and it is cleared the moment the agent produces anything \u2014 a reply, a tool call, or the end of the call. It also never runs while the agent is speaking, while a tool is running, before the caller's first turn, or on a call whose replies come from a workflow. A call ended this way reports `ended_reason` `agent_unresponsive` with `reason_detail` `model_unresponsive` (calls completed before 2026-09-25 reported `model_session_lost` with the same detail). On by default at the platform's own bound; an absent section is that default and is not stored.",
        "properties": {
          "seconds": {
            "description": "The bound, in seconds. `0` switches it off, and is the only value outside the range: an agent that waits on something genuinely slow may need it. Otherwise 10 to 120. The platform's own bound applies when unset.",
            "type": "number"
          }
        },
        "type": "object"
      },
      "AgentNavigation": {
        "additionalProperties": false,
        "description": "Whether the AI agent answers automated phone menus and waits in hold queues on the calls it places, and for how long. On by default: an agent that never chose (`enabled` null) navigates, and a customer who does not want it turns it off once with `enabled: false`, which every save keeps. A write of this section must say what `enabled` is: `true` (on), `false` (off) or `null` (follow the platform default, which is on). An empty object, `null` for the whole section, or a section without `enabled` is refused with 400 `navigation_enabled_required` (an older client used `{}` for off, and it is never guessed at). The section is replaced whole: limits you leave out return to their defaults. Agent create accepts the same section as `metadata.navigation` under the same rules; the generic agent update keeps the stored section and refuses a different one, naming this route. Navigation applies only to outbound calls (calls the agent places, campaign calls included); inbound calls (a phone test you dial in to included), browser tests, simulations, chat and SMS never navigate. Time in menus and on hold is billed at the normal call rate, and a campaign retries a call that ended at the hold limit or the call's maximum length, or whose other side hung up during the hold, without a person reached earlier (running out of the menu limit is final), so a waiting call can cost far more than one that ends at once. While a call is in a menu or on hold, the answer-detection non-human-audio ending is suspended; turn navigation off if an agent must hang up on hold music. The two limits are per agent and apply to the whole call, never to one menu or one queue: time spent in menus and time spent on hold are counted separately, and the call also ends if it reaches its overall maximum length (`turn_taking.max_call_seconds`). A single call cannot change them. While navigation is on (chosen or by default), a limit longer than the AI agent's maximum call length (`turn_taking.max_call_seconds`, at most 1740 seconds) is refused with 400, because a hold or a menu cannot outlast the call; the same refusal applies when you shorten the maximum call length below a limit already set, and when you switch navigation back on beside such a limit. A limit kept beside `enabled: false` is kept as it is. Applied when a call starts; saving never changes an in-progress call, and no republish is needed. Saving and publishing are never refused because an agent cannot navigate yet: an agent on GPT-Live, or one whose languages navigation is not released in (it is released in English, German, French, Spanish, Italian, Dutch, Portuguese, Russian and Polish; an agent qualifies when at least one of its languages is released and the transcribers Skysay supports can listen to all of its languages with at most two connections; its menus in the unreleased ones are then not navigated), keeps its setting, its calls run without navigation, and the voice-stack response says why in `navigation.eligible` and `navigation.ineligible_reason`. Such agents start navigating when support ships, with no change on your side. The tool names `press_digits`, `wait_on_hold` and `abandon_navigation` are reserved for navigation and refused for your own tools.",
        "properties": {
          "effective": {
            "description": "Read-only (see AgentNavigationStatus). Accepted and ignored on write, so a section read back from GET can be sent as it is.",
            "readOnly": true,
            "type": "boolean"
          },
          "eligible": {
            "description": "Read-only (see AgentNavigationStatus). Accepted and ignored on write.",
            "readOnly": true,
            "type": "boolean"
          },
          "enabled": {
            "description": "`true` turns phone-menu navigation and hold waiting on for this agent's outbound calls, `false` turns it off and is kept by every save, and `null` follows the platform default (on). Required in every write of this section.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "ineligible_reason": {
            "description": "Read-only (see AgentNavigationStatus). Accepted and ignored on write.",
            "readOnly": true,
            "type": [
              "string",
              "null"
            ]
          },
          "max_hold_seconds": {
            "default": 600,
            "description": "The most time a call may spend waiting in a hold queue before it ends. When it runs out the call ends with `ended_reason` `ivr_timeout` and `reason_detail` `hold_limit`.",
            "maximum": 3600,
            "minimum": 10,
            "type": "integer"
          },
          "max_navigation_seconds": {
            "default": 180,
            "description": "The most time a call may spend answering phone menus before it ends. When it runs out the call ends with `ended_reason` `ivr_timeout` and `reason_detail` `navigation_limit`.",
            "maximum": 900,
            "minimum": 10,
            "type": "integer"
          }
        },
        "required": [
          "enabled"
        ],
        "type": "object"
      },
      "AgentNavigationStatus": {
        "description": "The agent's phone-menu navigation as it stands: the stored choice, what it resolves to, both limits, and whether its outbound calls may navigate. Always present on GET. `eligible` describes the agent, not one call: the call's direction, its reservation and the voice worker still decide each call, and each call's own evidence says what happened.",
        "properties": {
          "effective": {
            "description": "Whether the setting is on: the stored choice, or the platform default (on) when it is null.",
            "type": "boolean"
          },
          "eligible": {
            "description": "Whether this agent's outbound calls may navigate.",
            "type": "boolean"
          },
          "enabled": {
            "description": "The stored choice: `true`, `false`, or `null` when the agent never chose and follows the platform default.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "ineligible_reason": {
            "description": "Why `eligible` is false, else null: `turned_off` (`enabled` is false), `not_released` (navigation is switched off platform-wide), `engine_not_supported` (the agent's conversation engine, GPT-Live today, does not navigate yet), `languages_unreleased` (navigation is released in none of the agent's languages, such as an agent that speaks only Estonian or Finnish), `languages_uncovered` (no supported transcriber covers one of the agent's languages), `too_many_sockets` (covering the agent's languages would need a third transcriber connection), `no_language` (the agent has no language), `tool_name_conflict` (one of the agent's own tools uses a reserved navigation tool name), or `tool_budget` (the agent's own tools leave no room under the 32-name tool contract for the three navigation tools).",
            "enum": [
              "turned_off",
              "not_released",
              "engine_not_supported",
              "languages_unreleased",
              "languages_uncovered",
              "too_many_sockets",
              "no_language",
              "tool_name_conflict",
              "tool_budget",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "max_hold_seconds": {
            "description": "The hold limit, with the default applied.",
            "type": "integer"
          },
          "max_navigation_seconds": {
            "description": "The menu limit, with the default applied.",
            "type": "integer"
          }
        },
        "required": [
          "enabled",
          "effective",
          "max_hold_seconds",
          "max_navigation_seconds",
          "eligible",
          "ineligible_reason"
        ],
        "type": "object"
      },
      "AgentOpening": {
        "additionalProperties": false,
        "description": "Outbound opening policy shared across conversation engines. Only managed outbound telephone calls use this wait; inbound and browser tests retain their normal opening. Saved by voice-stack PATCH and frozen in each call publication. Empty object resets to callee-first with two seconds of observed silence. Speech is allowed to finish beyond this wait. The timer authorizes the opening request, not first audible sound; provider generation and network latency add time.",
        "properties": {
          "first_speaker": {
            "default": "callee",
            "enum": [
              "callee",
              "agent"
            ],
            "type": "string"
          },
          "silence_wait_seconds": {
            "default": 2,
            "description": "Silence before requesting the first message when first_speaker is callee. Preserved but unused with agent-first.",
            "maximum": 5,
            "minimum": 1,
            "type": "number"
          }
        },
        "type": "object"
      },
      "AgentTemplateMessage": {
        "description": "Everything POST /v1/messages takes that names no resource, plus the template and what fills it. body, campaign_id, provider and segment_count are refused. The sending number, under any of its names, must belong to the agent's project.",
        "properties": {
          "bulk": {},
          "categories": {},
          "country": {},
          "from_number_id": {},
          "from_phone_number_id": {},
          "idempotency_key": {
            "type": "string"
          },
          "language": {
            "description": "The language to send in. Text is chosen from the call or request language, then its base language (et-EE -> et), then the agent's primary language, then, as a last resort, the template's texts in language-code order.",
            "type": "string"
          },
          "message_class": {},
          "metadata": {
            "type": "object"
          },
          "number_id": {},
          "project_id": {
            "description": "Optional. The agent's own project; any other answers 404 exactly as a foreign agent does.",
            "type": "string"
          },
          "purpose": {
            "description": "The message's purpose, authorized as on POST /v1/messages.",
            "type": "string"
          },
          "recipient_type": {},
          "relationship": {},
          "slots": {
            "additionalProperties": {
              "type": "string"
            },
            "description": "Values for the template's model slots, each within its max_length.",
            "type": "object"
          },
          "template_id": {
            "type": "string"
          },
          "to": {
            "description": "The recipient, E.164. to_phone_number is accepted as its alias.",
            "type": "string"
          },
          "to_phone_number": {},
          "user_authorization": {},
          "user_requested": {},
          "variables": {
            "description": "Values for the agent's declared variables the template references. A referenced required variable must be present; protected variables are refused (there is no confirmation to lean on).",
            "type": "object"
          }
        },
        "required": [
          "template_id"
        ],
        "type": "object"
      },
      "AgentTool": {
        "description": "A legacy string placeholder, a built-in tool, a signed webhook tool, or an attached Integration Action. The names `press_digits`, `wait_on_hold` and `abandon_navigation` are reserved for phone-menu navigation and refused with 400.",
        "oneOf": [
          {
            "description": "Legacy tool placeholder.",
            "type": "string"
          },
          {
            "$ref": "#/components/schemas/BuiltinAgentTool"
          },
          {
            "$ref": "#/components/schemas/WebhookAgentTool"
          },
          {
            "$ref": "#/components/schemas/IntegrationActionAttachment"
          }
        ]
      },
      "AgentToolWait": {
        "additionalProperties": false,
        "description": "What the caller hears while a slow tool call (an integration action, a knowledge search) is still running. Off unless a mode is set. `sound` plays a quiet keyboard-typing bed on the separate outgoing track once the tool has run for `delay_ms`, cut the instant the tool finishes, capped at 15 seconds; it is wordless, so it works in every language. `acknowledge` speaks one short phrase in the agent's own voice and language instead; without phrases the platform's reviewed list for the call language is used, and a language without one stays silent. Feedback never plays over the caller or the agent, at most once per tool wait, and never for the built-in end-call or transfer tools.",
        "properties": {
          "delay_ms": {
            "default": 1000,
            "description": "How long a tool may run before feedback starts. Most tools finish well inside this.",
            "maximum": 3000,
            "minimum": 600,
            "type": "integer"
          },
          "mode": {
            "default": "off",
            "enum": [
              "off",
              "acknowledge",
              "sound"
            ],
            "type": "string"
          },
          "phrases": {
            "description": "For `acknowledge` only; the same bounded neutral-phrase rule as acknowledgements. Rejected with `sound`.",
            "items": {
              "maxLength": 40,
              "minLength": 1,
              "type": "string"
            },
            "maxItems": 6,
            "type": "array"
          }
        },
        "type": "object"
      },
      "AgentTurnTaking": {
        "additionalProperties": false,
        "description": "Per-agent barge-in and endpointing tuning. Every field is optional and omitted from responses when it is at its default, so an agent that has never configured this section has no `turn_taking` key at all. Applied when a call starts; saving never changes an in-progress call.",
        "properties": {
          "barge_in_speech_rms": {
            "deprecated": true,
            "description": "Deprecated. Accepted, stored and returned for compatibility only; this runtime does not apply it to call behavior. Use `interruption_sensitivity`, `min_speech_ms` and `min_interruption_words` to control interruptions.",
            "maximum": 32000,
            "minimum": 100,
            "type": "number"
          },
          "interruption_sensitivity": {
            "default": "balanced",
            "description": "Hosted phone calls in every language. Preset interruption threshold: balanced 500 ms (the default), low 750 ms, high 150 ms. Preset end-of-turn hold on a language with the trained turn detector, the wait when the detector judges the caller finished: balanced 600 ms (the default), low 900 ms, high 260 ms; and the longest wait when it judges them probably not finished, before the turn ends anyway: balanced 1200 ms (the default), low 1800 ms, high 600 ms. On a language without the detector, where the hold is the whole end-of-turn decision: balanced 800 ms (the default), low 1100 ms, high 600 ms. Phone lines get these longer holds than a browser test because a caller pausing mid-sentence (after their name, before a number, while leaving a message) must not have their turn ended for them; `low` holds longer still, and `high` replies sooner. An explicit `min_speech_ms` or `silence_hold_ms` takes precedence over the preset for its knob. The end-of-turn hold is a minimum wait, not the reply time: a turn never ends before voice detection reports the end of speech, which takes about 250 ms of silence, or before the final transcript arrives, which depends on the speech-to-text provider.",
            "enum": [
              "balanced",
              "low",
              "high"
            ],
            "type": "string"
          },
          "max_call_seconds": {
            "default": 1740,
            "description": "How long a call may run before the platform ends it and reports `ended_reason: max_call_duration_reached`. Defaults to 1740 (29 minutes), which is also the ceiling: the underlying media session is dropped at 30 minutes, and a limit above that could never fire, so the call would end with no reason attached. Lower it to cap a call sooner; a value outside the range is refused rather than clamped.",
            "maximum": 1740,
            "minimum": 60,
            "type": "integer"
          },
          "min_interruption_words": {
            "description": "Defaults to 1 on hosted ENGLISH phone calls and to 0 everywhere else; set 0 explicitly to disable it. When set, an interruption must ALSO carry at least this many recognized words from the caller, in addition to the speech-duration threshold; it never lowers that threshold. On hosted ENGLISH calls, short listening noises (\"mm-hm\", \"uh-huh\", \"yeah\", \"okay\", \"right\", \"sure\", and filler words such as \"uh\") do not count toward it, so a caller showing they are following does not interrupt; every word counts in other languages. Not English-only, but effective only on stacks whose transcriber reports words fast enough to land inside the decision window (today the Deepgram stacks, English and Estonian). Elsewhere it is stored, returned unchanged, and ignored at call time.",
            "maximum": 5,
            "minimum": 0,
            "type": "integer"
          },
          "min_speech_ms": {
            "description": "Hosted phone calls in every language. Explicit continuous-speech threshold before an interruption is considered; it overrides any sensitivity preset. On a phone call the value is honoured down to 150 ms; a value below 150 ms is raised to 150 ms. A phone call with no value set and no sensitivity preset uses the 500 ms default. Browser and web calls do not apply this value at all: it is still stored and returned, but those calls keep the stack's own threshold.",
            "maximum": 2000,
            "minimum": 80,
            "type": "integer"
          },
          "no_caller_speech_seconds": {
            "default": 20,
            "description": "How long after the call is answered the platform waits to hear the other party before ending the call and reporting `ended_reason: no_caller_speech_after_answer`. Defaults to 20. Speech is detected from the audio itself, not from a transcript, and once the other party has been heard once the rule does not apply again for the rest of that call. The floor is 10 because the witness is voice activity: a caller who is merely listening looks the same as one who is not there, and a shorter value ends the call partway through the agent's own opening line.",
            "maximum": 120,
            "minimum": 10,
            "type": "integer"
          },
          "silence_hold_ms": {
            "description": "Hosted phone calls in every language. The shortest wait after the caller stops speaking before their turn can end; it overrides the platform default hold and any sensitivity preset's shortest wait (the preset's longest wait still applies on a language with the trained turn detector, and is raised to this value when it is shorter). It is a minimum, not the reply time: a value below about 250 ms has no effect, because voice detection needs that much silence to report the end of speech, and the wait is longer whenever the final transcript arrives later than this value.",
            "maximum": 2000,
            "minimum": 100,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "AgentUnrecognisedSpeech": {
        "additionalProperties": false,
        "description": "What the agent does when the caller speaks and the transcriber produces nothing at all \u2014 most often a caller using a language the agent's transcriber is not listening for, but also a bad line or a dropout. The agent says one short line in the call's own language telling the caller it could not understand them and which languages it can help in, then keeps listening; it never guesses what was said, never calls a tool and never ends the call. It is spoken only after the caller has stopped talking and the platform's wait for a transcript has expired, so it cannot talk over a live caller, and it is skipped entirely on an agent with follow-the-caller enabled, where the language switch answers instead. On by default, and spoken at most twice per call unless you change `max_per_call`. Send `enabled: false` to switch it off; an absent section is the default and is not stored.",
        "properties": {
          "enabled": {
            "description": "`false` switches the line off for this agent, and is the only value stored: the behaviour is on for every agent that says nothing.",
            "type": "boolean"
          },
          "max_per_call": {
            "description": "How many times one call may hear it. Two when unset.",
            "maximum": 5,
            "minimum": 1,
            "type": "integer"
          },
          "phrase": {
            "description": "Your own wording, spoken verbatim in place of the line the agent would otherwise compose in the call's language. Write it in the agent's language: it is not translated.",
            "maxLength": 300,
            "type": "string"
          }
        },
        "type": "object"
      },
      "AgentUpdate": {
        "description": "Mutable agent configuration. The target agent, rather than organization_id or project_id in this body, defines the authenticated workspace boundary. Voice-stack settings are not part of this payload: opening, turn_taking, voice_polish, answer_detection, navigation, audio_environment, follow_caller, languages and language_profiles are saved by PATCH /v1/agents/{agent_id}/voice-stack, and sending one here is refused with 400 naming that route rather than accepted and dropped.",
        "properties": {
          "audio_stream_url": {
            "type": "string"
          },
          "begin_message": {
            "type": "string"
          },
          "expected_tools": {
            "description": "Optional previously read complete tools array, including read-only refs. With a tools replacement, a mismatch returns 409 with error AgentToolsChangedError and writes nothing. Workspace editors and calendar attachment send it to avoid lost changes.",
            "items": {
              "$ref": "#/components/schemas/AgentTool"
            },
            "type": "array"
          },
          "first_message": {
            "type": "string"
          },
          "language": {
            "type": "string"
          },
          "metadata": {
            "description": "Replaces the agent's free-form metadata. The sections the voice-stack route owns are kept as stored when you leave them out, and a different value for one of them is refused with 400 naming PATCH /v1/agents/{agent_id}/voice-stack: `follow_caller`, `opening` and `navigation` (so a phone-menu navigation choice, including an explicit off, survives any metadata replacement).",
            "type": "object"
          },
          "mode": {
            "enum": [
              "external_audio",
              "external_text",
              "hosted"
            ],
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "recording_enabled": {
            "type": "boolean"
          },
          "status": {
            "description": "Whether this agent is offered to callers. `inactive` stops it being admitted on every call path -- inbound and outbound telephony, the browser test, and the public homepage demo -- while keeping the agent, its configuration and its number attachments intact; a call already in progress is not torn down. `active` removes that gate and nothing else: no configuration is snapshotted or restored, so the agent is then admitted on its CURRENT published configuration and readiness, which may have changed while it was inactive. Accepts these two values only; any other is refused with 400, as is any change to an agent whose stored status is not already one of them (an operator-suspended agent is reinstated by an operator only).",
            "enum": [
              "active",
              "inactive"
            ],
            "type": "string"
          },
          "system_prompt": {
            "type": "string"
          },
          "tools": {
            "description": "Complete agent tool allowlist. To attach an Integration Action, use an AgentTool object whose type is connection.",
            "items": {
              "$ref": "#/components/schemas/AgentTool"
            },
            "type": "array"
          },
          "translation": {
            "$ref": "#/components/schemas/TranslationPolicy"
          },
          "voice": {
            "oneOf": [
              {
                "description": "Legacy pinned voice identifier.",
                "type": "string"
              },
              {
                "description": "Exact selected TTS provider/model/voice inventory item.",
                "properties": {
                  "model": {
                    "type": "string"
                  },
                  "provider": {
                    "type": "string"
                  },
                  "voice_id": {
                    "description": "Provider inventory id, when the selected provider exposes voices.",
                    "type": "string"
                  }
                },
                "required": [
                  "provider",
                  "model"
                ],
                "type": "object"
              }
            ]
          },
          "webhook_url": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "AgentVariables": {
        "properties": {
          "agent_id": {
            "type": "string"
          },
          "config": {
            "$ref": "#/components/schemas/VariablesConfig"
          },
          "published_at": {
            "description": "When the current publication was made; empty before the first.",
            "type": "string"
          },
          "revision": {
            "description": "The agent's latest published language-profile version; send it back as base_revision. Any publish of the agent moves it. 0 before the agent is first published.",
            "minimum": 0,
            "type": "integer"
          },
          "schema_hash": {
            "description": "sha256 of the variable schema a call admitted now would carry; empty when the publication declares none a call could be admitted with.",
            "type": "string"
          }
        },
        "required": [
          "agent_id",
          "config",
          "revision",
          "schema_hash",
          "published_at"
        ],
        "type": "object"
      },
      "AgentVariablesUpdate": {
        "additionalProperties": false,
        "properties": {
          "base_revision": {
            "description": "The revision this edit was based on. When sent and stale, the write is refused with 409 revision_conflict. Omit for last-write-wins.",
            "minimum": 0,
            "type": "integer"
          },
          "config": {
            "$ref": "#/components/schemas/VariablesConfig"
          }
        },
        "required": [
          "config"
        ],
        "type": "object"
      },
      "AgentVoicePolish": {
        "additionalProperties": false,
        "description": "Per-agent voice polish. Absent keys keep the platform defaults; the response omits the section entirely when nothing is configured.",
        "properties": {
          "acknowledgements": {
            "$ref": "#/components/schemas/AgentAcknowledgements"
          },
          "delivery": {
            "description": "How the selected voice performs its speech. Unset keeps the provider's own defaults, which is what every agent had before this field existed. Each profile maps to allowlisted provider controls only where the selected TTS provider/model truthfully exposes them: ElevenLabs voice settings (eleven_v3 supports stability only), a bounded Gemini TTS synthesis instruction, and a speaking-rate multiplier for Soniox and Cartesia. Fish Audio and Azure neural voices accept the setting but apply no change. The voice preview (POST /v1/voice-previews with the same `delivery`) uses the identical mapping, so an audition sounds like the call.",
            "enum": [
              "natural",
              "lively",
              "calm",
              "precise"
            ],
            "type": "string"
          },
          "end_of_call_phrase": {
            "maxLength": 300,
            "type": "string"
          },
          "model_unresponsive": {
            "$ref": "#/components/schemas/AgentModelUnresponsive"
          },
          "tool_wait": {
            "$ref": "#/components/schemas/AgentToolWait"
          },
          "unrecognised_speech": {
            "$ref": "#/components/schemas/AgentUnrecognisedSpeech"
          }
        },
        "type": "object"
      },
      "AgentVoiceStack": {
        "description": "Safe read projection. It never includes component credential binding IDs, plaintext/ciphertext, nonce, fingerprint, or provider secrets.",
        "properties": {
          "agent_id": {
            "type": "string"
          },
          "answer_detection": {
            "$ref": "#/components/schemas/AgentAnswerDetection"
          },
          "audio_environment": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/AudioEnvironmentSelection"
              },
              {
                "$ref": "#/components/schemas/AudioEnvironmentInvalidSelection"
              }
            ]
          },
          "cost": {
            "type": "object"
          },
          "first_message": {
            "type": "string"
          },
          "follow_caller": {
            "$ref": "#/components/schemas/AgentFollowCaller"
          },
          "language": {
            "type": "string"
          },
          "language_profiles": {
            "$ref": "#/components/schemas/AgentLanguageProfiles"
          },
          "latency": {
            "type": "object"
          },
          "model": {
            "type": "object"
          },
          "navigation": {
            "$ref": "#/components/schemas/AgentNavigationStatus"
          },
          "opening": {
            "$ref": "#/components/schemas/AgentOpening"
          },
          "organization_id": {
            "type": "string"
          },
          "project_id": {
            "type": "string"
          },
          "selectable": {
            "type": "boolean"
          },
          "system_prompt": {
            "type": "string"
          },
          "transcriber": {
            "type": "object"
          },
          "turn_taking": {
            "$ref": "#/components/schemas/AgentTurnTaking"
          },
          "voice": {
            "description": "The stored TTS selection as the agent catalog projects it, including `delivery_control` (`voice_settings`, `instruction`, `speed` or `unsupported`): whether a `voice_polish.delivery` profile changes this voice's audio, and `delivery_profiles`: the exact value each profile resolves to on this engine (empty for `unsupported`), both read from the packaged delivery table.",
            "type": "object"
          },
          "voice_frontend": {
            "description": "The conversation engine; cascade is the default. Always present on GET; omitted on older rows means cascade. Whether this engine has a language axis is declared in the agent-catalog conversation_engines block (has_language_axis).",
            "enum": [
              "cascade",
              "gpt_live",
              "gemini_live"
            ],
            "type": "string"
          },
          "voice_label": {
            "type": "string"
          },
          "voice_polish": {
            "$ref": "#/components/schemas/AgentVoicePolish"
          },
          "warnings": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "agent_id",
          "organization_id",
          "project_id",
          "language",
          "transcriber",
          "model",
          "voice",
          "cost",
          "latency",
          "selectable",
          "warnings"
        ],
        "type": "object"
      },
      "AgentVoiceStackUpdate": {
        "description": "Selection/update payload. It carries only selectors, existing credential IDs, tools, and tuning -- never provider secret material. Supplying languages replaces the selected set; supplying language_profiles replaces sparse overrides, while omission preserves them. After language opt-in, shared prompt, stack, or credential saves explicitly republish inherited effective profiles.",
        "properties": {
          "answer_detection": {
            "$ref": "#/components/schemas/AgentAnswerDetection"
          },
          "audio_environment": {
            "$ref": "#/components/schemas/AudioEnvironmentSelection"
          },
          "begin_message": {
            "description": "Legacy alias for first_message.",
            "type": "string"
          },
          "component_credential_bindings": {
            "additionalProperties": {
              "type": [
                "string",
                "null"
              ]
            },
            "description": "Optional stt/llm/tts map to existing hosted-AI credential IDs (or null to use platform-managed selection). References are validated; secret upload is a separate write-only API.",
            "type": "object"
          },
          "expected_tools": {
            "description": "Optional previously read complete tools array, including read-only refs. With a tools replacement, a mismatch returns 409 with error AgentToolsChangedError and writes nothing. Workspace editors and calendar attachment send it to avoid lost changes.",
            "items": {
              "$ref": "#/components/schemas/AgentTool"
            },
            "type": "array"
          },
          "first_message": {
            "type": "string"
          },
          "follow_caller": {
            "$ref": "#/components/schemas/AgentFollowCaller"
          },
          "language": {
            "type": "string"
          },
          "language_profile_revision": {
            "description": "Optional optimistic-concurrency revision returned by GET. A stale value returns conflict; reload before retrying.",
            "minimum": 0,
            "type": "integer"
          },
          "language_profiles": {
            "description": "When supplied this replaces the sparse override set; omit it to preserve overrides. An engine without a language axis (see voice_frontend and the agent-catalog conversation_engines block) REFUSES this field with 400 rather than accepting and dropping it.",
            "items": {
              "$ref": "#/components/schemas/AgentLanguageProfileOverride"
            },
            "maxItems": 32,
            "type": "array"
          },
          "languages": {
            "description": "When supplied this replaces the selected language set. An engine without a language axis (see voice_frontend and the agent-catalog conversation_engines block) REFUSES this field with 400 rather than accepting and dropping it.",
            "items": {
              "maxLength": 35,
              "minLength": 2,
              "type": "string"
            },
            "maxItems": 32,
            "minItems": 1,
            "type": "array"
          },
          "model": {
            "$ref": "#/components/schemas/VoiceComponentSelection"
          },
          "navigation": {
            "$ref": "#/components/schemas/AgentNavigation"
          },
          "opening": {
            "$ref": "#/components/schemas/AgentOpening"
          },
          "system_prompt": {
            "type": "string"
          },
          "tools": {
            "items": {
              "$ref": "#/components/schemas/AgentTool"
            },
            "type": "array"
          },
          "transcriber": {
            "$ref": "#/components/schemas/VoiceComponentSelection"
          },
          "turn_taking": {
            "$ref": "#/components/schemas/AgentTurnTaking"
          },
          "voice": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/VoiceComponentSelection"
              },
              {
                "description": "Legacy voice label.",
                "type": "string"
              }
            ]
          },
          "voice_frontend": {
            "description": "Optional conversation engine. Omitted or cascade is STT/LLM/TTS. gpt_live selects GPT-Live-1. GET still returns transcriber, model, and voice as an OpenAI compatibility projection. Each engine declares whether it has a language axis; a voice-to-voice engine does not, and this route then refuses languages, language_profiles and follow_caller with a 400 naming the field and the engine. Read has_language_axis from the agent-catalog conversation_engines block rather than assuming it from the id.",
            "enum": [
              "cascade",
              "gpt_live",
              "gemini_live"
            ],
            "type": "string"
          },
          "voice_polish": {
            "$ref": "#/components/schemas/AgentVoicePolish"
          }
        },
        "type": "object"
      },
      "AgentWorkflowRead": {
        "properties": {
          "agent_id": {
            "type": "string"
          },
          "published": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/WorkflowVersionSummary"
              },
              {
                "type": "null"
              }
            ]
          },
          "version_count": {
            "minimum": 0,
            "type": "integer"
          },
          "versions": {
            "items": {
              "$ref": "#/components/schemas/WorkflowVersionSummary"
            },
            "type": "array"
          },
          "versions_page": {
            "$ref": "#/components/schemas/OffsetPage"
          },
          "workflow": {
            "description": "Editable draft/revision, validation, and safe canvas-only layout when workflow_state=draft; otherwise null.",
            "properties": {
              "agent_id": {
                "type": "string"
              },
              "created_at": {
                "type": "string"
              },
              "draft": {
                "type": "object"
              },
              "layout": {
                "description": "Only positions, viewport, and collapsed_groups; arbitrary stored layout keys are not returned.",
                "type": "object"
              },
              "name": {
                "type": "string"
              },
              "revision": {
                "minimum": 0,
                "type": "integer"
              },
              "source": {
                "type": "string"
              },
              "updated_at": {
                "type": "string"
              },
              "validation": {
                "type": "object"
              },
              "workflow_id": {
                "type": "string"
              }
            },
            "type": [
              "object",
              "null"
            ]
          },
          "workflow_state": {
            "enum": [
              "not_created",
              "draft"
            ],
            "type": "string"
          }
        },
        "required": [
          "agent_id",
          "workflow_state",
          "workflow",
          "published",
          "version_count",
          "versions",
          "versions_page"
        ],
        "type": "object"
      },
      "AudioEnvironmentCatalogue": {
        "properties": {
          "catalogue_version": {
            "const": "v1",
            "type": "string"
          },
          "presets": {
            "items": {
              "properties": {
                "asset_version": {
                  "type": "string"
                },
                "content_note": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "duration_ms": {
                  "type": "integer"
                },
                "name": {
                  "type": "string"
                },
                "preset_id": {
                  "type": "string"
                },
                "volume": {
                  "properties": {
                    "default": {
                      "type": "integer"
                    },
                    "maximum": {
                      "type": "integer"
                    },
                    "minimum": {
                      "type": "integer"
                    }
                  },
                  "required": [
                    "minimum",
                    "maximum",
                    "default"
                  ],
                  "type": "object"
                }
              },
              "required": [
                "preset_id",
                "name",
                "description",
                "asset_version",
                "volume"
              ],
              "type": "object"
            },
            "maxItems": 6,
            "minItems": 6,
            "type": "array"
          },
          "schema": {
            "const": "skysay.audio_environment_catalog.v1",
            "type": "string"
          }
        },
        "required": [
          "schema",
          "catalogue_version",
          "presets"
        ],
        "type": "object"
      },
      "AudioEnvironmentInvalidSelection": {
        "additionalProperties": false,
        "description": "A historic malformed saved pin, returned visibly instead of silently becoming Off. Choose a supported selection or Off before publishing again.",
        "properties": {
          "asset_version": {
            "type": "string"
          },
          "preset_id": {
            "type": "string"
          },
          "status": {
            "const": "invalid",
            "type": "string"
          },
          "volume": {
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "preset_id",
          "asset_version",
          "volume",
          "status"
        ],
        "type": "object"
      },
      "AudioEnvironmentSelection": {
        "additionalProperties": false,
        "properties": {
          "asset_version": {
            "description": "Immutable packaged asset version; it is never inferred.",
            "enum": [
              "v1"
            ],
            "type": "string"
          },
          "preset_id": {
            "description": "Exact catalogue id. `off` has no asset.",
            "type": "string"
          },
          "volume": {
            "description": "Whole-number selection. Off requires 0; enabled presets require 1 through 100.",
            "maximum": 100,
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "preset_id",
          "asset_version",
          "volume"
        ],
        "type": "object"
      },
      "BillingSettings": {
        "properties": {
          "auto_recharge_enabled": {
            "type": "boolean"
          },
          "organization_id": {
            "type": "string"
          },
          "payment_provider": {
            "enum": [
              "stripe"
            ],
            "type": "string"
          },
          "recharge_amount_minor": {
            "type": "integer"
          },
          "threshold_minor": {
            "type": "integer"
          }
        },
        "required": [
          "organization_id"
        ],
        "type": "object"
      },
      "BuiltinAgentTool": {
        "description": "A built-in tool the AI agent can invoke during a call. `phone_number` is conditionally required: see its own description.",
        "properties": {
          "connection_id": {
            "description": "Required for calendar builtins: one active, owner-configured calendar connection in the AI agent's organization/project. The model cannot select it.",
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "name": {
            "enum": [
              "end_call",
              "send_sms",
              "transfer_to_human",
              "request_callback",
              "set_call_disposition",
              "calendar_check_availability",
              "calendar_book_appointment"
            ],
            "type": "string"
          },
          "phone_number": {
            "description": "Destination for `transfer_to_human`, and required when that tool is enabled. Must be full E.164 including the leading `+` and country code (for example `+12025550100`); a blank, partial, or non-dialable number is rejected when the agent is created or updated, rather than failing mid-call. Ignored by `end_call` and `send_sms`.",
            "type": "string"
          },
          "ref": {
            "description": "Read-only, server-issued. A stable opaque identifier for this configured tool, used to name the tool on a call's transcript rows without storing the tool's own name with them. Ignored if you send it: the value is taken from what is already stored, so it cannot be chosen. It survives any edit that keeps the tool's `name` and `type`; renaming, retyping or removing a tool retires its identifier, and transcript rows recorded before that show the tool's kind instead of a name.",
            "readOnly": true,
            "type": "string"
          },
          "type": {
            "const": "builtin"
          }
        },
        "required": [
          "type",
          "name"
        ],
        "type": "object"
      },
      "CalendarConfiguration": {
        "properties": {
          "allow_test_writes": {
            "default": false,
            "type": "boolean"
          },
          "buffer_minutes": {
            "default": 0,
            "maximum": 120,
            "minimum": 0,
            "type": "integer"
          },
          "conflict_ids": {
            "items": {
              "type": "string"
            },
            "maxItems": 10,
            "type": "array"
          },
          "duration_minutes": {
            "default": 30,
            "maximum": 240,
            "minimum": 5,
            "type": "integer"
          },
          "horizon_days": {
            "default": 30,
            "maximum": 60,
            "minimum": 1,
            "type": "integer"
          },
          "invite_caller": {
            "default": false,
            "type": "boolean"
          },
          "location": {
            "maxLength": 200,
            "type": "string"
          },
          "minimum_notice_minutes": {
            "default": 60,
            "maximum": 10080,
            "minimum": 0,
            "type": "integer"
          },
          "target_id": {
            "maxLength": 512,
            "type": "string"
          },
          "test_connection": {
            "default": false,
            "type": "boolean"
          },
          "timezone": {
            "description": "IANA timezone identifier",
            "type": "string"
          },
          "working_hours": {
            "additionalProperties": {
              "items": {
                "maximum": 1440,
                "minimum": 0,
                "type": "integer"
              },
              "maxItems": 2,
              "minItems": 2,
              "type": "array"
            },
            "description": "Monday=0 through Sunday=6; each value is [start,end] in local minutes since midnight. No overnight intervals.",
            "type": "object"
          }
        },
        "required": [
          "target_id",
          "timezone"
        ],
        "type": "object"
      },
      "Call": {
        "description": "The call resource GET /v1/calls returns, and GET /v1/calls/{call_id} returns as CallDetail (this plus workflow). A synthetic simulation or platform-internal room is never returned here; only a call the customer or their agent actually made.",
        "properties": {
          "agent_endpoint_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "agent_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "answer_classification": {
            "description": "What was heard after the call was answered, which is a different question from whether it was answered at all: a carrier bridges audio for a ringback replacement tune or a hold-music loop exactly as it does for a person, so the status says the line answered and this says what was on it. `human` means a person was heard. `non_human_audio` means nothing a person did was ever heard: continuous music or tones held the line, or a recording repeated the same cycle of things over and over; `reason_detail` names which kind. `silent` means the line answered and nothing at all was heard. `unknown` means the detector ran and reached no decision inside its window, which is deliberately NOT the same as the empty string: empty means no verdict was reported at all, on a call placed before this field shipped, on an agent whose collection is switched off, or on a call whose media never started. Detection is deliberately narrow and only claims `non_human_audio` for unbroken non-human audio, so a recorded announcement or an IVR menu with pauses in it reads as `human` or `unknown` rather than being classified here -- unless it repeats itself, which is the one text-based verdict, `looping_recording`.",
            "enum": [
              "",
              "human",
              "non_human_audio",
              "silent",
              "unknown"
            ],
            "type": "string"
          },
          "browser_outcome": {
            "description": "Specific-call detail only. A managed workspace browser worker's frozen outcome, readable before transport completion. Empty means unknown. browser_startup_failed means the requested opening failed before observed agent output; worker_ended confirms worker teardown. Neither changes transport status or promises a refund.",
            "enum": [
              "",
              "browser_startup_failed",
              "worker_ended"
            ],
            "type": "string"
          },
          "call_context": {
            "description": "The small, non-secret JSON object you supplied at call creation, without any variable the agent declares as protected or as a keypad field.",
            "type": "object"
          },
          "country": {
            "description": "Destination (outbound) or number (inbound) country.",
            "type": "string"
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "direction": {
            "description": "inbound, outbound, web_test (a live browser test), or homepage_demo (a conversation with one of your AI agents in the live demo on the Skysay homepage, recorded like a call and never billed to your wallet) for a call your agent placed or answered. A call seeded by the demo/artifact-import tooling can carry other values, such as imported, verbatim from that import.",
            "type": "string"
          },
          "ended_reason": {
            "description": "Why the call ended. `browser_startup_failed` means a workspace browser test requested an opening that failed before observed agent output; it does not promise a refund. `funding_exhausted` means the last funded runtime allowance expired; `reason_detail` is `insufficient_balance` for a confirmed funding refusal or `renewal_unavailable` when renewal could not be confirmed. Holds estimate usage and do not cap the final cost. Other reasons fall into three groups. Two are the ordinary end of a call that went well: `agent_completed` when the agent finished the conversation and ended the call itself, which is the usual outcome of a workflow that runs to its last step, and `remote_party_disconnected` when the other party hung up. Six are endings the platform initiates: `agent_unresponsive`, described below, `hangup_requested` when someone ends the call from the workspace or through POST /v1/calls/{call_id}/hangup, `max_call_duration_reached` when the call hits its configured limit, `no_caller_speech_after_answer` when the call is answered but nobody ever speaks, `no_human_after_answer` when the call is answered but nothing on the line was a person: only music or tones were ever heard, or a recording repeated the same cycle of things over and over, and `caller_unresponsive` when both sides fell silent after the agent had spoken: after 30 seconds of silence the agent asks whether the caller is still there (once per silent stretch, at most three times per call), and the call ends politely 15 seconds after that question if nobody answers. This ending is rolling out: while it is in observation the platform records what it would have done and does not end calls, so the value does not yet appear. A caller who answers, even with one word, simply continues the conversation. The last two name an ending nobody chose, neither the people on the call nor the platform: `media_session_lost`, when the media session went away underneath a live call -- the room was deleted, or the connection to it failed -- and `model_session_lost`, when the conversation engine behind the call failed and could not be recovered while the media session was still up, so the platform ended the call rather than leave the line open with nobody on it. `agent_unresponsive` is the platform ending a call whose agent stopped answering while its engine stayed connected: the caller finished speaking and the agent produced nothing for the whole of the agent's `voice_polish.model_unresponsive` bound, which counts only the time the caller was NOT speaking. It reports `reason_detail` `model_unresponsive`. It says what was observed -- a silent agent -- and not that anything failed, because a voice-to-voice engine reports nothing when it stops answering. The same ending, with an empty `reason_detail`, closes a call whose agent could not produce its opening: when the opening fails before anything is heard, the platform asks for it once more if that cannot repeat or overlap anything already said, and if the opening still never reaches the caller it ends the call instead of leaving them in silence (on an incoming call answered by a voice-to-voice engine, which already hears and answers the caller, the call continues instead). Calls completed before 2026-09-25 reported this ending as `model_session_lost` with the same `reason_detail`. In all three, nobody hung up, no limit fired and the agent did not finish. Each reports what was observed rather than guessing at a cause, so a call that ends this way is not reported as an operator hang-up even when an operator had just asked for one; that request is answered separately and says the call was already gone. `ivr_timeout` is the platform ending a call that was waiting in a phone menu or a hold queue (phone menu navigation); `reason_detail` says why: `hold_limit` (the hold time ran out), `navigation_limit` (the time allowed in menus ran out), `deadline` (the call's overall maximum length was reached while waiting), `menu_no_path` (the menu offered no way to a person) or `data_not_authorised` (the menu asked for a value the call did not supply or may not give). When the other party hangs up while a call is waiting in a menu or on hold, the reason stays `remote_party_disconnected` and `reason_detail` is `during_hold`. `no_human_after_answer` can carry `recorded_message_only` (a recording and nothing else answered) and `media_session_lost` can carry `navigation_detector_lost`. Empty on a call that completed before this field shipped, and on a call that has not completed. A call that FAILED reports its reason in `technical_failure.failure_reason_code` instead; the two vocabularies are separate.",
            "enum": [
              "",
              "hangup_requested",
              "remote_party_disconnected",
              "agent_completed",
              "max_call_duration_reached",
              "no_caller_speech_after_answer",
              "media_session_lost",
              "no_human_after_answer",
              "model_session_lost",
              "agent_unresponsive",
              "caller_unresponsive",
              "browser_startup_failed",
              "funding_exhausted",
              "ivr_timeout"
            ],
            "type": "string"
          },
          "estimated_cost_minor": {
            "description": "The wallet reservation estimate in minor currency units, made at admission time. This is not settled billing: some statuses hold no reservation at all (see NO_LEDGER_MOVEMENT statuses), and a completed call's real charge is settled separately from recording/artifact import.",
            "type": "integer"
          },
          "external_group_reference": {
            "type": "string"
          },
          "external_reference": {
            "description": "Your own stable case/reference ID, echoed back verbatim; empty string when not set. The same value delivered in webhook event bodies.",
            "type": "string"
          },
          "from_e164": {
            "type": [
              "string",
              "null"
            ]
          },
          "from_number_id": {
            "description": "Your owned number this call used, when applicable.",
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "person_reached": {
            "description": "Whether a person was reached earlier in the call, as the platform's phone-menu and hold handling reported it with the ending: `true` when someone was spoken to before the call ended (a conversation, then a transfer into a queue that ran out of time), `false` when nobody was reached, and null when the call did not report it (a call that completed before this field shipped, one whose agent does not navigate phone menus, and one that ended another way). It is what separates a wait that ran out after a conversation from one that never reached anyone: `ivr_timeout` with `hold_limit` or `deadline`, and `remote_party_disconnected` with `during_hold`, are retried by a campaign only when this is `false`, and the recording of such a call counts as talk time (and the call as a pick-up) only when it is `true`.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "project_id": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "provider_call_id": {
            "description": "The carrier/provider's own call identifier. Empty until the call is actually dialed out to the provider.",
            "type": [
              "string",
              "null"
            ]
          },
          "purpose": {
            "description": "Required, free-form outbound purpose declaration -- a non-empty purpose is the compliance paper trail; a novel purpose token is never refused for being novel. Documented catalog (pick one or send your own token): appointment, booking_confirmation, callback_requested, customer_outreach, delivery_notification, ops_alert, other, payment_reminder, personal_errand, reservation, status_check, support.",
            "type": "string"
          },
          "reason_detail": {
            "description": "Qualifies either `answer_classification` or `ended_reason`, and says which by the value. `insufficient_balance` and `renewal_unavailable` qualify only `funding_exhausted`: confirmed insufficient funds versus unavailable authorization, respectively. `caller_tune`, `hold_music` and `looping_recording` appear only with `answer_classification` `non_human_audio`, and `no_audio` only with `silent`: `looping_recording` means the far end said the same cycle of things over and over, a recording on repeat, rather than music or tones. `model_unresponsive` appears with `ended_reason` `agent_unresponsive` -- and, on calls completed before 2026-09-25, with `model_session_lost` -- and means the call was ended by the platform because the agent produced nothing at all for the whole of its bound of caller-free time after the caller finished speaking -- as opposed to the same reason reached because the conversation engine reported its own failure. `hold_limit`, `navigation_limit`, `deadline`, `menu_no_path` and `data_not_authorised` appear only with `ended_reason` `ivr_timeout`, `during_hold` only with `remote_party_disconnected`, `recorded_message_only` only with `no_human_after_answer` and `navigation_detector_lost` only with `media_session_lost`, as described on `ended_reason`. Empty when neither needs qualifying, which is most calls. This is not `ended_reason` itself: that one names the ending, this one adds to it.",
            "enum": [
              "",
              "caller_tune",
              "data_not_authorised",
              "deadline",
              "during_hold",
              "hold_limit",
              "hold_music",
              "insufficient_balance",
              "looping_recording",
              "menu_no_path",
              "model_unresponsive",
              "navigation_detector_lost",
              "navigation_limit",
              "no_audio",
              "recorded_message_only",
              "renewal_unavailable"
            ],
            "type": "string"
          },
          "recipient_type": {
            "description": "Optional, never enforced or required -- outbound is not restricted to business recipients. Persisted verbatim on the call row if sent; SMS has no recipient_type column, so it is accepted but not stored there.",
            "type": "string"
          },
          "recording": {
            "description": "Present only once a recording exists for this call.",
            "properties": {
              "deleted_at": {
                "format": "date-time",
                "type": [
                  "string",
                  "null"
                ]
              },
              "duration_seconds": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "kind": {
                "type": "string"
              },
              "media_url": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "retention_days": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "status": {
                "type": "string"
              },
              "transcript_status": {
                "description": "partial means a transcript exists but is missing the text of at least one spoken agent reply.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "type": "object"
          },
          "recording_consent_state": {
            "type": "string"
          },
          "recording_disclosure": {
            "$ref": "#/components/schemas/CallRecordingState"
          },
          "recording_enabled": {
            "type": "boolean"
          },
          "recording_state": {
            "$ref": "#/components/schemas/CallRecordingState"
          },
          "recording_status": {
            "description": "not_started or disabled when there is no recording yet, otherwise the latest recording's own status.",
            "type": "string"
          },
          "refusal": {
            "description": "Why the call was refused before it dialled; null on every other call. A call refused before dialling has `status` `policy_blocked` or `needs_review`. `stage` is the gate that refused it: `outbound_authorization` (the workspace capabilities: destination, channel, use-case profile, review signals, caps) or `outbound_policy` (the per-call policy: `purpose`, `user_authorization`, caller-number readiness, destination format). `message` is a sentence you can act on, e.g. naming a missing `user_authorization.reason`. POST /v1/policies/authorize-outbound asks the same two questions before you call.",
            "properties": {
              "decision": {
                "enum": [
                  "blocked",
                  "needs_review"
                ],
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "reason_codes": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "required_actions": {
                "description": "For `outbound_policy`: what to add, e.g. `provide_user_authorization.reason`. Empty for `outbound_authorization`.",
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "stage": {
                "enum": [
                  "outbound_authorization",
                  "outbound_policy"
                ],
                "type": "string"
              }
            },
            "required": [
              "stage",
              "decision",
              "reason_codes",
              "required_actions",
              "message"
            ],
            "type": [
              "object",
              "null"
            ]
          },
          "runtime_config": {
            "description": "Your own per-call overrides captured at call start (e.g. instructions, first_message, language). Never provider credentials.",
            "type": "object"
          },
          "source_kind": {
            "description": "Always 'live' on this customer-facing route -- a simulation-suite or other synthetic room is not found here at all.",
            "enum": [
              "live"
            ],
            "type": "string"
          },
          "status": {
            "description": "Call lifecycle status, e.g. requested, queued, dialing, ringing, answered, in_progress, media_started, completed, failed, busy, no_answer, canceled, live_blocked, policy_blocked, needs_review (held for review: never dialled, with a call.failed at once). Not a closed set: track it as an opaque string and rely on the call.* events for exact transitions.",
            "type": "string"
          },
          "to_e164": {
            "type": [
              "string",
              "null"
            ]
          },
          "updated_at": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "organization_id",
          "project_id",
          "direction",
          "status",
          "from_number_id",
          "from_e164",
          "to_e164",
          "country",
          "recipient_type",
          "purpose",
          "agent_id",
          "agent_endpoint_id",
          "provider",
          "provider_call_id",
          "estimated_cost_minor",
          "recording_enabled",
          "recording_consent_state",
          "recording_status",
          "recording_state",
          "recording_disclosure",
          "runtime_config",
          "external_reference",
          "external_group_reference",
          "call_context",
          "source_kind",
          "ended_reason",
          "answer_classification",
          "reason_detail",
          "person_reached",
          "refusal",
          "created_at",
          "updated_at"
        ],
        "type": "object"
      },
      "CallArtifactImport": {
        "properties": {
          "label": {
            "type": "string"
          },
          "mode": {
            "enum": [
              "auto",
              "folder_per_call",
              "file_per_call"
            ],
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "project_id": {
            "type": "string"
          },
          "source_path": {
            "type": "string"
          }
        },
        "required": [
          "organization_id",
          "project_id",
          "source_path"
        ],
        "type": "object"
      },
      "CallClosingEvidence": {
        "additionalProperties": false,
        "properties": {
          "capture_finished": {
            "type": "boolean"
          },
          "completeness": {
            "enum": [
              "complete",
              "partial",
              "unavailable",
              "not_applicable"
            ]
          },
          "counts": {
            "additionalProperties": false,
            "properties": {
              "accepted": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              },
              "already_finished": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              },
              "failed": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              },
              "joined": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              },
              "media_ended": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              },
              "owner_changed": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              },
              "owner_commit": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              },
              "provider_close_sent": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              },
              "reclaimed": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              },
              "refused": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              },
              "request": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              }
            },
            "type": "object"
          },
          "dropped": {
            "additionalProperties": false,
            "properties": {
              "accepted": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              },
              "already_finished": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              },
              "failed": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              },
              "joined": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              },
              "media_ended": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              },
              "owner_changed": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              },
              "owner_commit": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              },
              "provider_close_sent": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              },
              "reclaimed": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              },
              "refused": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              },
              "request": {
                "maximum": 9007199254740991,
                "minimum": 0,
                "type": "integer"
              }
            },
            "type": "object"
          },
          "ending_reason": {
            "enum": [
              "",
              "hangup_requested",
              "remote_party_disconnected",
              "agent_completed",
              "max_call_duration_reached",
              "no_caller_speech_after_answer",
              "media_session_lost",
              "no_human_after_answer",
              "model_session_lost",
              "agent_unresponsive",
              "caller_unresponsive",
              "browser_startup_failed",
              "funding_exhausted",
              "ivr_timeout"
            ]
          },
          "final_state": {
            "enum": [
              "ended",
              "not_recorded"
            ]
          },
          "items": {
            "items": {
              "$ref": "#/components/schemas/CallClosingReceipt"
            },
            "maxItems": 200,
            "type": "array"
          },
          "limit": {
            "const": 200
          },
          "reason": {
            "enum": [
              "",
              "not_recorded",
              "processing",
              "recording_deleted",
              "malformed",
              "conflict"
            ]
          },
          "returned": {
            "maximum": 200,
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "completeness",
          "reason",
          "counts",
          "dropped",
          "items",
          "capture_finished",
          "final_state",
          "returned",
          "limit"
        ],
        "type": "object"
      },
      "CallClosingReceipt": {
        "additionalProperties": false,
        "properties": {
          "ending_reason": {
            "enum": [
              "",
              "hangup_requested",
              "remote_party_disconnected",
              "agent_completed",
              "max_call_duration_reached",
              "no_caller_speech_after_answer",
              "media_session_lost",
              "no_human_after_answer",
              "model_session_lost",
              "agent_unresponsive",
              "caller_unresponsive",
              "browser_startup_failed",
              "funding_exhausted",
              "ivr_timeout"
            ]
          },
          "event_id": {
            "maxLength": 96,
            "type": "string"
          },
          "kind": {
            "const": "closing"
          },
          "offset_ms": {
            "maximum": 9007199254740991,
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "offset_precision": {
            "enum": [
              "exact",
              "unavailable"
            ]
          },
          "outcome_not_recorded": {
            "type": "boolean"
          },
          "outside_recording_range": {
            "type": "boolean"
          },
          "owner": {
            "enum": [
              "model",
              "platform",
              "remote",
              "transport"
            ]
          },
          "proposal_id": {
            "maxLength": 96,
            "type": "string"
          },
          "reason": {
            "enum": [
              "",
              "already_finished",
              "caller_speech",
              "guard",
              "interrupted",
              "media_disconnect",
              "navigation",
              "platform_rule",
              "runtime_error",
              "transfer"
            ]
          },
          "request_id": {
            "maxLength": 96,
            "type": "string"
          },
          "sequence": {
            "maximum": 9007199254740991,
            "minimum": 1,
            "type": "integer"
          },
          "source": {
            "enum": [
              "call_lifecycle",
              "close_owner",
              "native_provider",
              "participant",
              "room"
            ]
          },
          "stage": {
            "enum": [
              "accepted",
              "already_finished",
              "call_ended",
              "failed",
              "joined",
              "media_ended",
              "owner_changed",
              "owner_commit",
              "provider_close_sent",
              "reclaimed",
              "refused",
              "request"
            ]
          }
        },
        "required": [
          "kind",
          "stage",
          "sequence",
          "offset_ms",
          "offset_precision",
          "event_id",
          "source",
          "owner",
          "reason",
          "request_id",
          "proposal_id",
          "outside_recording_range",
          "outcome_not_recorded"
        ],
        "type": "object"
      },
      "CallDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Call"
          },
          {
            "properties": {
              "workflow": {
                "description": "null when the call ran no workflow (every GPT-Live and Gemini Live call).",
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/CallWorkflow"
                  },
                  {
                    "type": "null"
                  }
                ]
              }
            },
            "required": [
              "workflow"
            ],
            "type": "object"
          }
        ],
        "description": "The call GET /v1/calls/{call_id} returns: the Call resource plus where its workflow ended. The GET /v1/calls list returns Call without it."
      },
      "CallEvent": {
        "description": "One row of a call's internal lifecycle log, oldest first. Not the webhook envelope: this row's own id/type/payload, never the signed delivery body. Includes many types never delivered to any webhook endpoint at all.",
        "properties": {
          "call_id": {
            "type": "string"
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "page_cursor": {
            "description": "This row's position in the timeline. Pass the LAST row's value as `after` to continue from here. Opaque: its encoding is not part of the contract, and it is not the row's id.",
            "type": "string"
          },
          "payload": {
            "description": "Event-specific detail; shape varies by writer, not just by type -- call.failed alone has at least four shapes: a policy block carries {reason, reasons}; local/queued-origination failure carries {reason} (the worker-queue originate failure adds worker_job_id and error); a reconciler repair carries {source, reason, previous_status, evidence, observed_at}; a gateway-lifecycle failure (SIP/room) carries an unrelated {gateway_event_id, gateway_id, source_component, source, observed_at, details}. Always thinner than the richer result body the call.failed webhook delivers separately.",
            "type": "object"
          },
          "type": {
            "description": "e.g. call.created, call.queued, call.dialing, call.answered, media.started, call.ended, call.failed, call.canceled, call.live_blocked, plus internal-only bookkeeping types such as wallet.reserved, wallet.settled, call.authorization, call.policy_evaluated, number.matched. call.completed is the one terminal type never written here -- it is only ever delivered to webhooks.",
            "type": "string"
          }
        },
        "required": [
          "id",
          "call_id",
          "organization_id",
          "type",
          "payload",
          "created_at",
          "page_cursor"
        ],
        "type": "object"
      },
      "CallGroupResultItem": {
        "properties": {
          "analysis": {
            "$ref": "#/components/schemas/PostCallAnalysis"
          },
          "answered_or_voicemail": {
            "description": "Observed call.answered/media.started evidence: the transport answered. It is not a human answer classification and can be voicemail, a caller tune or hold music. Read `call.answer_classification` for what was actually heard on the line.",
            "type": "boolean"
          },
          "call": {
            "$ref": "#/components/schemas/CallResultCallReference"
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "page_cursor": {
            "type": "string"
          },
          "technical_failure": {
            "$ref": "#/components/schemas/TechnicalCallFailureEvidence"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "call",
          "created_at",
          "updated_at",
          "page_cursor",
          "answered_or_voicemail",
          "technical_failure",
          "analysis"
        ],
        "type": "object"
      },
      "CallGroupResults": {
        "properties": {
          "calls": {
            "items": {
              "$ref": "#/components/schemas/CallGroupResultItem"
            },
            "maxItems": 100,
            "type": "array"
          },
          "external_group_reference": {
            "type": "string"
          },
          "filters": {
            "type": "object"
          },
          "pagination": {
            "properties": {
              "has_more": {
                "type": "boolean"
              },
              "limit": {
                "maximum": 100,
                "minimum": 1,
                "type": "integer"
              },
              "next_cursor": {
                "type": "string"
              },
              "returned": {
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "limit",
              "returned",
              "has_more",
              "next_cursor"
            ],
            "type": "object"
          },
          "summary": {
            "description": "Factual counters only. answered_or_voicemail publishes numerator and denominator; human_answered/voicemail remain unavailable unless independently observable. Per-call answer classification is published on each item rather than summarised here, because a summary counter would imply a coverage this summary cannot promise for calls placed before the classification existed.",
            "properties": {
              "artifacts": {
                "type": "object"
              },
              "calls": {
                "type": "object"
              },
              "extraction": {
                "type": "object"
              },
              "metrics": {
                "type": "object"
              }
            },
            "type": "object"
          },
          "version": {
            "minimum": 1,
            "type": "integer"
          }
        },
        "required": [
          "version",
          "external_group_reference",
          "summary",
          "filters",
          "calls",
          "pagination"
        ],
        "type": "object"
      },
      "CallPerformanceDashboard": {
        "properties": {
          "call": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "properties": {
                  "created_at": {
                    "type": "string"
                  },
                  "direction": {
                    "type": "string"
                  },
                  "id": {
                    "type": "string"
                  },
                  "status": {
                    "type": "string"
                  },
                  "updated_at": {
                    "type": "string"
                  }
                },
                "required": [
                  "id",
                  "direction",
                  "status",
                  "created_at",
                  "updated_at"
                ],
                "type": "object"
              }
            ]
          },
          "evidence": {
            "properties": {
              "reason": {
                "type": "string"
              },
              "status": {
                "enum": [
                  "complete",
                  "partial",
                  "unavailable"
                ],
                "type": "string"
              }
            },
            "required": [
              "status",
              "reason"
            ],
            "type": "object"
          },
          "me": {
            "properties": {
              "wallet": {
                "description": "Compact authenticated workspace wallet-header snapshot.",
                "type": "object"
              }
            },
            "required": [
              "wallet"
            ],
            "type": "object"
          },
          "organization": {
            "description": "Authenticated organization projection.",
            "type": "object"
          },
          "project": {
            "description": "Authenticated project projection.",
            "type": "object"
          },
          "spans_page": {
            "properties": {
              "has_more": {
                "type": "boolean"
              },
              "limit": {
                "type": "integer"
              },
              "returned": {
                "type": "integer"
              }
            },
            "required": [
              "limit",
              "returned",
              "has_more"
            ],
            "type": "object"
          },
          "turns": {
            "items": {
              "properties": {
                "spans": {
                  "items": {
                    "properties": {
                      "attempt": {
                        "type": "integer"
                      },
                      "confidence": {
                        "type": "string"
                      },
                      "duration_ms": {
                        "type": "integer"
                      },
                      "fallback": {
                        "type": "boolean"
                      },
                      "measurement_source": {
                        "type": "string"
                      },
                      "model": {
                        "type": "string"
                      },
                      "outcome": {
                        "type": "string"
                      },
                      "provider": {
                        "type": "string"
                      },
                      "reason_code": {
                        "type": "string"
                      },
                      "retry": {
                        "type": "boolean"
                      },
                      "stage": {
                        "type": "string"
                      },
                      "start_offset_ms": {
                        "type": "integer"
                      },
                      "tool_name": {
                        "type": "string"
                      }
                    },
                    "required": [
                      "stage",
                      "attempt",
                      "start_offset_ms",
                      "duration_ms",
                      "outcome"
                    ],
                    "type": "object"
                  },
                  "type": "array"
                },
                "turn_index": {
                  "minimum": 0,
                  "type": "integer"
                }
              },
              "required": [
                "turn_index",
                "spans"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "version": {
            "const": 1,
            "type": "integer"
          }
        },
        "required": [
          "version",
          "organization",
          "project",
          "me",
          "call",
          "evidence",
          "turns",
          "spans_page"
        ],
        "type": "object"
      },
      "CallRecording": {
        "description": "Metadata for the active recording. Use download_url for playback; storage_key and media_url are legacy implementation details, not download contracts.",
        "properties": {
          "call_id": {
            "type": "string"
          },
          "content_type": {
            "type": "string"
          },
          "disclosure": {
            "$ref": "#/components/schemas/CallRecordingState"
          },
          "download_url": {
            "description": "Relative authenticated API URL for full or byte-range recording playback.",
            "type": "string"
          },
          "duration_seconds": {
            "type": "number"
          },
          "id": {
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "project_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "organization_id",
          "call_id",
          "status",
          "content_type",
          "duration_seconds",
          "download_url",
          "disclosure"
        ],
        "type": "object"
      },
      "CallRecordingState": {
        "description": "Safe recording-disclosure state (services/calls_routing/calls.py:_call_recording_state_from_rows). recording_state and recording_disclosure on Call both carry this same object.",
        "properties": {
          "active_recording_id": {
            "description": "Empty string unless the latest recording is undeleted and available.",
            "type": "string"
          },
          "audit_actions": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "consent_notice": {
            "type": "string"
          },
          "consent_state": {
            "type": "string"
          },
          "deletable": {
            "type": "boolean"
          },
          "deleted_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "downloadable": {
            "type": "boolean"
          },
          "duration_seconds": {
            "type": "number"
          },
          "kind": {
            "type": "string"
          },
          "media_url": {
            "description": "Empty string unless a recording is currently active.",
            "type": "string"
          },
          "recording_enabled": {
            "type": "boolean"
          },
          "recording_id": {
            "description": "Empty string when there is no recording yet.",
            "type": "string"
          },
          "retention_days": {
            "type": "integer"
          },
          "status": {
            "description": "not_started or disabled when there is no recording yet, otherwise the latest recording's own status (e.g. processing, available, deleted).",
            "type": "string"
          },
          "visible_to_customer": {
            "type": "boolean"
          }
        },
        "required": [
          "recording_enabled",
          "status",
          "recording_id",
          "active_recording_id",
          "kind",
          "media_url",
          "duration_seconds",
          "consent_state",
          "consent_notice",
          "retention_days",
          "deleted_at",
          "visible_to_customer",
          "downloadable",
          "deletable",
          "audit_actions"
        ],
        "type": "object"
      },
      "CallResultCallReference": {
        "description": "Authoritative technical call facts, intentionally separate from advisory extraction.",
        "properties": {
          "answer_classification": {
            "description": "What was heard after the answer, with the same meanings as on the Call resource. It sits here rather than with the analysis because it is an observation of the line, not a reading of the conversation.",
            "enum": [
              "",
              "human",
              "non_human_audio",
              "silent",
              "unknown"
            ],
            "type": "string"
          },
          "call_id": {
            "type": "string"
          },
          "direction": {
            "type": "string"
          },
          "external_group_reference": {
            "type": "string"
          },
          "external_reference": {
            "type": "string"
          },
          "reason_detail": {
            "description": "Qualifies answer_classification or ended_reason, with the same meanings as on the Call resource.",
            "enum": [
              "",
              "caller_tune",
              "data_not_authorised",
              "deadline",
              "during_hold",
              "hold_limit",
              "hold_music",
              "insufficient_balance",
              "looping_recording",
              "menu_no_path",
              "model_unresponsive",
              "navigation_detector_lost",
              "navigation_limit",
              "no_audio",
              "recorded_message_only",
              "renewal_unavailable"
            ],
            "type": "string"
          },
          "status": {
            "type": "string"
          }
        },
        "required": [
          "call_id",
          "direction",
          "status",
          "answer_classification",
          "reason_detail"
        ],
        "type": "object"
      },
      "CallStats": {
        "properties": {
          "agent_window": {
            "description": "Present only when you requested one.",
            "type": "string"
          },
          "agents": {
            "description": "Present only when agent_window was requested: one bounded page of agents that have placed or received a call in this scope, ordered by agent_id. Read agents_has_more before treating it as the whole set.",
            "items": {
              "$ref": "#/components/schemas/CallStatsAgent"
            },
            "type": "array"
          },
          "agents_has_more": {
            "description": "Present only when agent_window was requested. True when the organization has more agents than this page holds; the page is NOT the whole set unless this is false.",
            "type": "boolean"
          },
          "agents_limit": {
            "description": "Present only when agent_window was requested: the page size actually applied, after clamping.",
            "type": "integer"
          },
          "agents_next_cursor": {
            "description": "Present only when agent_window was requested. Non-empty only when agents_has_more is true: pass it as agent_after to continue. Empty string on the last page.",
            "type": "string"
          },
          "agents_returned": {
            "description": "Present only when agent_window was requested: agents in this page.",
            "type": "integer"
          },
          "organization_id": {
            "type": "string"
          },
          "project_id": {
            "description": "Empty string when the request was not scoped to one project.",
            "type": "string"
          },
          "until": {
            "format": "date-time",
            "type": "string"
          },
          "windows": {
            "additionalProperties": {
              "$ref": "#/components/schemas/CallStatsWindow"
            },
            "description": "Keyed by the window key you supplied in the repeated window=<key>:<since> query parameter.",
            "type": "object"
          }
        },
        "required": [
          "organization_id",
          "project_id",
          "until",
          "windows"
        ],
        "type": "object"
      },
      "CallStatsAgent": {
        "properties": {
          "agent_id": {
            "type": "string"
          },
          "calls": {
            "description": "Calls for this agent within agent_window.",
            "type": "integer"
          },
          "last_call_at": {
            "description": "Most recent call for this agent across the whole history, not just agent_window.",
            "format": "date-time",
            "type": "string"
          },
          "spend_minor": {
            "description": "Same estimated_cost_minor sum as CallStatsWindow.spend_minor, scoped to this agent within agent_window.",
            "type": "integer"
          }
        },
        "required": [
          "agent_id",
          "calls",
          "spend_minor",
          "last_call_at"
        ],
        "type": "object"
      },
      "CallStatsWindow": {
        "properties": {
          "answered": {
            "description": "Calls in this window somebody was on: completed, or currently answered/in_progress/media_started. Never merely accepted by the platform.",
            "type": "integer"
          },
          "calls": {
            "description": "Every call in this window, including blocked and never-billable ones.",
            "type": "integer"
          },
          "failed": {
            "description": "Calls in this window that ended with nobody on them: failed, busy, no_answer, canceled, refused by a policy or live-call gate, or held for review (needs_review). answered + failed need not equal calls -- a dialing or queued call is in neither -- so do not derive one from the other.",
            "type": "integer"
          },
          "since": {
            "format": "date-time",
            "type": "string"
          },
          "spend_minor": {
            "description": "Sum of estimated_cost_minor across every call in this window. This is the wallet reservation estimate made at admission time, not settled billing -- excludes calls that never cost anything: never reserved (policy_blocked, needs_review) or canceled before dialling.",
            "type": "integer"
          },
          "talk_seconds": {
            "type": "integer"
          },
          "until": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "since",
          "until",
          "calls",
          "spend_minor",
          "talk_seconds",
          "answered",
          "failed"
        ],
        "type": "object"
      },
      "CallTranscriptPage": {
        "properties": {
          "call_id": {
            "type": "string"
          },
          "events": {
            "items": {
              "properties": {
                "speaker": {
                  "const": "caller",
                  "type": "string"
                },
                "start_ms": {
                  "type": "integer"
                },
                "type": {
                  "const": "caller_spoke_no_transcript"
                }
              },
              "required": [
                "type",
                "speaker",
                "start_ms"
              ],
              "type": "object"
            },
            "maxItems": 500,
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/TranscriptPagination"
          },
          "source_revision": {
            "type": "integer"
          },
          "text": {
            "type": "string"
          },
          "timeline": {
            "items": {
              "$ref": "#/components/schemas/TranscriptTimelineItem"
            },
            "maxItems": 500,
            "type": "array"
          },
          "transcript_settled": {
            "type": "boolean"
          },
          "transcript_status": {
            "type": "string"
          },
          "translation": {
            "$ref": "#/components/schemas/CommunicationTranslation"
          },
          "turns": {
            "items": {
              "$ref": "#/components/schemas/TranscriptTurn"
            },
            "maxItems": 500,
            "type": "array"
          }
        },
        "required": [
          "call_id",
          "source_revision",
          "transcript_status",
          "transcript_settled",
          "pagination",
          "translation",
          "turns",
          "events",
          "timeline",
          "text"
        ],
        "type": "object"
      },
      "CallTranscriptStreamPage": {
        "description": "JSON envelope containing an ordered event page, with the NDJSON content type label preserved.",
        "properties": {
          "call_id": {
            "type": "string"
          },
          "content_type": {
            "const": "application/x-ndjson",
            "type": "string"
          },
          "events": {
            "items": {
              "$ref": "#/components/schemas/TranscriptTimelineItem"
            },
            "maxItems": 500,
            "type": "array"
          },
          "pagination": {
            "$ref": "#/components/schemas/TranscriptPagination"
          },
          "source_revision": {
            "type": "integer"
          },
          "transcript_settled": {
            "type": "boolean"
          },
          "transcript_status": {
            "type": "string"
          },
          "translation": {
            "$ref": "#/components/schemas/CommunicationTranslation"
          }
        },
        "required": [
          "call_id",
          "source_revision",
          "transcript_status",
          "transcript_settled",
          "pagination",
          "translation",
          "content_type",
          "events"
        ],
        "type": "object"
      },
      "CallWorkflow": {
        "additionalProperties": false,
        "description": "Where a call's conversation workflow ended, read the same way on the call detail, MCP get_call and the call.completed / call.failed webhooks. Ids, platform and author-declared strings, and the author's outcome label only; never a call value. The vocabularies are open: a later release may add values, so handle unknown ones. On a webhook this is a snapshot at emission; GET /v1/calls/{call_id} is the current read, and `settled` says whether it can still change. To act on a request not to be contacted, check `\"cease_contact\" in interrupts`: it is the one canonical stop signal, and nothing adds the number to the do-not-call register by itself.",
        "properties": {
          "interrupts": {
            "description": "Every distinct global interrupt the run took, in the order first taken; [] when none. Known values: wrong_party, dispute, hardship, cease_contact, callback, human_request, language_change. An interrupt is what the caller said that the workflow acted on; it can differ from the outcome reached (an interrupt may lead to an ask or a transfer, and a run can take one and later end on an ordinary route).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "outcome": {
            "description": "The step reached; null when the run reached no outcome step.",
            "oneOf": [
              {
                "additionalProperties": false,
                "properties": {
                  "label": {
                    "description": "The step's label: display text the author can change. Never match on it.",
                    "type": "string"
                  },
                  "step_id": {
                    "description": "The last outcome step the run reached, as authored.",
                    "type": "string"
                  },
                  "type": {
                    "description": "The step's declared outcome type. Known values: review_candidate, informational, terminal_note.",
                    "type": "string"
                  }
                },
                "required": [
                  "step_id",
                  "type",
                  "label"
                ],
                "type": "object"
              },
              {
                "type": "null"
              }
            ]
          },
          "settled": {
            "description": "true once the run is closed and nothing can change this object; false while a turn could still commit. An incomplete run on an ended call can be unsettled, can still change, and can stay unsettled: treat it as not final.",
            "type": "boolean"
          },
          "status": {
            "description": "Known values: running (the call is in progress), completed, transferred, failed (the workflow itself failed), incomplete (it stopped before completing or transferring, typically because the call ended first).",
            "type": "string"
          },
          "version": {
            "description": "The published version the run pinned; null when it pinned the draft (a Talk test or an inbound Phone test). A step_id resolves after later edits only against a published version.",
            "type": [
              "integer",
              "null"
            ]
          },
          "workflow_id": {
            "description": "The workflow the run pinned.",
            "type": "string"
          }
        },
        "required": [
          "workflow_id",
          "version",
          "status",
          "settled",
          "outcome",
          "interrupts"
        ],
        "type": "object"
      },
      "CallerInputEnvironmentSelection": {
        "additionalProperties": false,
        "description": "Simulation Lab only: a version-pinned condition mixed into the synthetic caller microphone. This is not the agent's outgoing audio_environment and never changes a production call's STT/TTS configuration.",
        "properties": {
          "asset_version": {
            "description": "Immutable packaged asset version; it is never inferred.",
            "enum": [
              "v1"
            ],
            "type": "string"
          },
          "noise_level": {
            "description": "Whole-number caller-input level. Off requires 0; enabled presets require 1 through 100.",
            "maximum": 100,
            "minimum": 0,
            "type": "integer"
          },
          "preset_id": {
            "description": "Exact catalogue id. `off` has no asset and is the quiet baseline.",
            "type": "string"
          }
        },
        "required": [
          "preset_id",
          "asset_version",
          "noise_level"
        ],
        "type": "object"
      },
      "CallingWindow": {
        "properties": {
          "end": {
            "pattern": "^\\d{2}:\\d{2}$",
            "type": "string"
          },
          "start": {
            "pattern": "^\\d{2}:\\d{2}$",
            "type": "string"
          }
        },
        "required": [
          "start",
          "end"
        ],
        "type": "object"
      },
      "CampaignCreate": {
        "properties": {
          "agent_id": {
            "type": "string"
          },
          "calling_windows": {
            "additionalProperties": {
              "items": {
                "$ref": "#/components/schemas/CallingWindow"
              },
              "type": "array"
            },
            "type": "object"
          },
          "consent_basis": {
            "enum": [
              "existing_customer",
              "explicit_consent"
            ],
            "type": "string"
          },
          "contact_list_id": {
            "type": "string"
          },
          "ends_at": {
            "format": "date-time",
            "type": "string"
          },
          "from_number_ids": {
            "items": {
              "type": "string"
            },
            "maxItems": 10,
            "minItems": 1,
            "type": "array"
          },
          "language": {
            "maxLength": 12,
            "type": "string"
          },
          "max_concurrency": {
            "minimum": 1,
            "type": "integer"
          },
          "name": {
            "maxLength": 120,
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "project_id": {
            "type": "string"
          },
          "purpose": {
            "type": "string"
          },
          "reserved_concurrency": {
            "minimum": 0,
            "type": "integer"
          },
          "retry_policy": {
            "additionalProperties": {
              "$ref": "#/components/schemas/RetryRule"
            },
            "type": "object"
          },
          "retry_policy_preset": {
            "enum": [
              "sales",
              "collections",
              "single_attempt"
            ],
            "type": "string"
          },
          "scheduled_start_at": {
            "format": "date-time",
            "type": "string"
          },
          "time_zone": {
            "default": "UTC",
            "description": "IANA time zone (default UTC), matched and stored like a contact's time_zone",
            "type": "string"
          },
          "time_zone_mode": {
            "default": "contact",
            "enum": [
              "contact",
              "fixed"
            ],
            "type": "string"
          }
        },
        "required": [
          "organization_id",
          "project_id",
          "name",
          "agent_id",
          "from_number_ids",
          "contact_list_id",
          "purpose",
          "consent_basis"
        ],
        "type": "object"
      },
      "CampaignUpdate": {
        "description": "Any subset of the campaign settings; which ones are accepted depends on the campaign status (see editable_fields on the campaign). `contacts` uploads a replacement audience: the list is created inside the same locked transaction and becomes contact_list_id, so a status that locks the audience answers 409 before any list exists.",
        "properties": {
          "agent_id": {
            "type": "string"
          },
          "calling_windows": {
            "additionalProperties": {
              "items": {
                "$ref": "#/components/schemas/CallingWindow"
              },
              "type": "array"
            },
            "type": "object"
          },
          "consent_basis": {
            "enum": [
              "existing_customer",
              "explicit_consent"
            ],
            "type": "string"
          },
          "contact_list_id": {
            "type": "string"
          },
          "contact_list_name": {
            "maxLength": 120,
            "type": "string"
          },
          "contact_list_source": {
            "description": "How the replacement audience was collected, recorded on the new list. Only read alongside `contacts`; defaults to `csv`.",
            "enum": [
              "api",
              "csv",
              "xlsx",
              "mcp"
            ],
            "type": "string"
          },
          "contacts": {
            "items": {
              "$ref": "#/components/schemas/ContactRow"
            },
            "maxItems": 10000,
            "type": "array"
          },
          "ends_at": {
            "format": "date-time",
            "type": "string"
          },
          "from_number_ids": {
            "items": {
              "type": "string"
            },
            "maxItems": 10,
            "minItems": 1,
            "type": "array"
          },
          "language": {
            "maxLength": 12,
            "type": "string"
          },
          "max_concurrency": {
            "minimum": 1,
            "type": "integer"
          },
          "name": {
            "maxLength": 120,
            "type": "string"
          },
          "purpose": {
            "type": "string"
          },
          "reserved_concurrency": {
            "minimum": 0,
            "type": "integer"
          },
          "retry_policy": {
            "additionalProperties": {
              "$ref": "#/components/schemas/RetryRule"
            },
            "type": "object"
          },
          "retry_policy_preset": {
            "enum": [
              "sales",
              "collections",
              "single_attempt"
            ],
            "type": "string"
          },
          "scheduled_start_at": {
            "format": "date-time",
            "type": "string"
          },
          "time_zone": {
            "description": "IANA time zone (default UTC), matched and stored like a contact's time_zone",
            "type": "string"
          },
          "time_zone_mode": {
            "enum": [
              "contact",
              "fixed"
            ],
            "type": "string"
          }
        },
        "type": "object"
      },
      "Capabilities": {
        "description": "The workspace's capabilities record: what it may do outbound. One per workspace, always. `active` authorizes outbound within the listed profiles, channels, destinations and limits; every other status authorizes nothing new.",
        "properties": {
          "all_countries": {
            "description": "True: every destination except the sanctioned set is authorized and `countries` is empty. False: `countries` is the explicit allowlist. Activation through verification sets it true; a request chooses.",
            "type": "boolean"
          },
          "approved_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "approved_by": {
            "description": "`self_serve` for a request approved within the catalog, the operator who approved it, or the verification door that activated it.",
            "type": "string"
          },
          "approved_profiles": {
            "items": {
              "enum": [
                "personal_assistant",
                "business_support",
                "appointments_reservations",
                "payment_reminder",
                "internal_operations",
                "customer_outreach",
                "implementation_services_custom"
              ],
              "type": "string"
            },
            "type": "array"
          },
          "calling_rules": {
            "description": "The application calling rules an operator recorded on approval; `{}` when none apply.",
            "type": "object"
          },
          "channels": {
            "items": {
              "enum": [
                "voice",
                "sms"
              ],
              "type": "string"
            },
            "type": "array"
          },
          "compliance_profile_id": {
            "description": "The verified business profile the record was activated against; empty until verification clears.",
            "type": "string"
          },
          "countries": {
            "description": "The destination allowlist as ISO 3166-1 alpha-2 codes when `all_countries` is false. A call or text to any other country is held for review.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "created_at": {
            "type": "string"
          },
          "customer_visible_message": {
            "description": "Server-composed status sentence for the workspace; display as-is.",
            "type": "string"
          },
          "destination_restrictions": {
            "description": "Optional customer narrowing, intersected with current capabilities at admission. null means no additional customer restriction.",
            "items": {
              "type": "string"
            },
            "nullable": true,
            "type": "array"
          },
          "destination_revision": {
            "description": "Revision required when saving optional destination restrictions.",
            "type": "integer"
          },
          "id": {
            "type": "string"
          },
          "limits": {
            "additionalProperties": {
              "type": "integer"
            },
            "description": "The profile caps projected into the workspace's risk controls at its last activation or approval (per-day/per-minute call and SMS counts, unique destinations, daily voice and wallet spend in minor units). Empty when no profile caps were projected. The caps enforced right now are the workspace's risk controls, which an operator may have changed since; `/v1/dashboard/capabilities` reports those.",
            "type": "object"
          },
          "metadata": {
            "type": "object"
          },
          "organization_id": {
            "type": "string"
          },
          "policy_accepted_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "policy_accepted_by": {
            "type": "string"
          },
          "policy_version": {
            "type": "string"
          },
          "status": {
            "enum": [
              "not_activated",
              "pending_review",
              "active",
              "suspended"
            ],
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "CapabilitiesDetail": {
        "description": "The record, its calling rules, the profiles the workspace may select from, and live usage against its caps.",
        "properties": {
          "available_profiles": {
            "items": {
              "$ref": "#/components/schemas/CapabilitiesProfile"
            },
            "type": "array"
          },
          "calling_rules": {
            "description": "The customer-visible projection of the application calling rules recorded during Skysay approval (effective period, time zone, calling windows, per-case cadence and attempt cap, escalation actions), or null when none apply. Configured by Skysay, never by this API.",
            "type": [
              "object",
              "null"
            ]
          },
          "calling_rules_active": {
            "description": "True when calling rules are set and the record is active, i.e. they are enforced on every call right now.",
            "type": "boolean"
          },
          "capabilities": {
            "$ref": "#/components/schemas/Capabilities"
          },
          "generated_at": {
            "type": "string"
          },
          "usage": {
            "$ref": "#/components/schemas/CapabilitiesUsage"
          }
        },
        "type": "object"
      },
      "CapabilitiesPolicyAcceptance": {
        "properties": {
          "accepted_by": {
            "type": "string"
          },
          "policy_version": {
            "description": "Required unless the record already carries one.",
            "type": "string"
          }
        },
        "type": "object"
      },
      "CapabilitiesProfile": {
        "description": "One selectable use-case profile and the caps it grants by default.",
        "properties": {
          "allowed_purposes": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "allowed_recipient_types": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "channels": {
            "items": {
              "enum": [
                "voice",
                "sms"
              ],
              "type": "string"
            },
            "type": "array"
          },
          "default_caps": {
            "additionalProperties": {
              "type": "integer"
            },
            "type": "object"
          },
          "description": {
            "type": "string"
          },
          "key": {
            "enum": [
              "personal_assistant",
              "business_support",
              "appointments_reservations",
              "payment_reminder",
              "internal_operations",
              "customer_outreach",
              "implementation_services_custom"
            ],
            "type": "string"
          },
          "label": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "CapabilitiesRequest": {
        "properties": {
          "accepted_by": {
            "type": "string"
          },
          "all_countries": {
            "description": "True authorizes every destination except the sanctioned set and ignores `countries`. Exactly one of `all_countries: true` or a non-empty `countries` is required; a request with neither is refused with 400 and nothing is written.",
            "type": "boolean"
          },
          "channels": {
            "description": "Omitted or empty falls back to the selected profiles' own channels.",
            "items": {
              "enum": [
                "voice",
                "sms"
              ],
              "type": "string"
            },
            "type": "array"
          },
          "countries": {
            "description": "The destination allowlist as ISO 3166-1 alpha-2 codes, used when `all_countries` is not true. An empty list is refused, and so is any entry that is not a well-formed two-letter code once normalized (the refusal names it). A few aliases are normalized (UAE, USA, UK).",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "type": "array"
          },
          "limits": {
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Optional cap overrides; `live_call_concurrency` is always taken from the plan and never from this payload.",
            "type": "object"
          },
          "metadata": {
            "description": "Free-form notes merged into the record. `calling_rules` and the retired `metadata.operational_profile` key are refused here: calling rules are set during Skysay approval.",
            "type": "object"
          },
          "policy_version": {
            "description": "Accepting the outbound policy here is what makes immediate activation possible for a verified workspace; without it the request goes to review.",
            "type": "string"
          },
          "profiles": {
            "description": "At least one valid use-case profile. Unknown keys are dropped; a selection with none left is refused.",
            "items": {
              "enum": [
                "personal_assistant",
                "business_support",
                "appointments_reservations",
                "payment_reminder",
                "internal_operations",
                "customer_outreach",
                "implementation_services_custom"
              ],
              "type": "string"
            },
            "minItems": 1,
            "type": "array"
          }
        },
        "required": [
          "profiles"
        ],
        "type": "object"
      },
      "CapabilitiesUsage": {
        "description": "Live usage for the same rolling windows the record's limits are expressed in.",
        "properties": {
          "outbound_calls_last_day": {
            "type": "integer"
          },
          "outbound_calls_last_minute": {
            "type": "integer"
          },
          "sms_last_day": {
            "type": "integer"
          },
          "unique_destinations_last_day": {
            "type": "integer"
          },
          "wallet_spend_last_day_minor": {
            "type": "integer"
          }
        },
        "type": "object"
      },
      "CommunicationTranslation": {
        "properties": {
          "catalog": {
            "properties": {
              "max_targets": {
                "const": 6,
                "type": "integer"
              },
              "supported_target_languages": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "tariff": {
                "properties": {
                  "currency": {
                    "const": "USD",
                    "type": "string"
                  },
                  "minimum_per_source_target": {
                    "const": 0.01,
                    "type": "number"
                  },
                  "rate_per_1000_words": {
                    "const": 0.02,
                    "type": "number"
                  },
                  "word_count_rule": {
                    "type": "string"
                  }
                },
                "type": "object"
              }
            },
            "required": [
              "supported_target_languages",
              "max_targets",
              "tariff"
            ],
            "type": "object"
          },
          "eligible": {
            "type": "boolean"
          },
          "pagination": {
            "description": "Selected call translation units follow the original transcript timeline's pages.",
            "properties": {
              "has_more": {
                "type": "boolean"
              },
              "limit": {
                "type": "integer"
              },
              "next_cursor": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "returned": {
                "type": "integer"
              }
            },
            "type": "object"
          },
          "quote": {
            "description": "Pre-inference upper bound using all original words. Language detection can reduce the settled price. Absent for unavailable or unquoted summary sources.",
            "properties": {
              "currency": {
                "type": "string"
              },
              "max_eligible_words": {
                "type": "integer"
              },
              "maximum_amount_minor": {
                "type": "integer"
              }
            },
            "type": "object"
          },
          "quote_pending": {
            "description": "The settled source is waiting for its current-revision word-count summary. Poll this pure read; quote is absent until ready.",
            "type": "boolean"
          },
          "source_completeness": {
            "description": "Partial indicates interrupted artifact delivery or unsettled source data; any available original text remains readable.",
            "enum": [
              "complete",
              "partial",
              "unavailable"
            ],
            "type": "string"
          },
          "source_id": {
            "type": "string"
          },
          "source_kind": {
            "enum": [
              "call",
              "message"
            ],
            "type": "string"
          },
          "source_revision": {
            "type": "integer"
          },
          "source_status": {
            "enum": [
              "not_settled",
              "unavailable",
              "empty",
              "unit_too_large",
              "too_large",
              "available"
            ],
            "type": "string"
          },
          "translations": {
            "items": {
              "properties": {
                "amount_minor": {
                  "type": "integer"
                },
                "currency": {
                  "type": "string"
                },
                "eligible_words": {
                  "type": "integer"
                },
                "id": {
                  "type": "string"
                },
                "reason": {
                  "type": "string"
                },
                "source_revision": {
                  "type": "integer"
                },
                "status": {
                  "enum": [
                    "not_requested",
                    "queued",
                    "running",
                    "ready",
                    "waiting_funds",
                    "failed",
                    "stale"
                  ],
                  "type": "string"
                },
                "target_language": {
                  "type": "string"
                },
                "units": {
                  "items": {
                    "properties": {
                      "id": {
                        "description": "The immutable original turn or message id.",
                        "type": "string"
                      },
                      "text": {
                        "maxLength": 16000,
                        "type": "string"
                      }
                    },
                    "required": [
                      "id",
                      "text"
                    ],
                    "type": "object"
                  },
                  "type": "array"
                }
              },
              "type": "object"
            },
            "maxItems": 49,
            "type": "array"
          }
        },
        "required": [
          "source_kind",
          "source_id",
          "source_revision",
          "eligible",
          "source_status",
          "translations",
          "catalog"
        ],
        "type": "object"
      },
      "ContactImport": {
        "properties": {
          "contacts": {
            "items": {
              "$ref": "#/components/schemas/ContactRow"
            },
            "maxItems": 10000,
            "minItems": 1,
            "type": "array"
          },
          "organization_id": {
            "type": "string"
          }
        },
        "required": [
          "organization_id",
          "contacts"
        ],
        "type": "object"
      },
      "ContactListCreate": {
        "properties": {
          "contacts": {
            "items": {
              "$ref": "#/components/schemas/ContactRow"
            },
            "maxItems": 10000,
            "type": "array"
          },
          "name": {
            "maxLength": 120,
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "project_id": {
            "type": "string"
          },
          "source": {
            "description": "How the rows were collected, recorded on the list. `xlsx` means the workspace read an Excel workbook in the browser and sent these rows; the API itself never parses a spreadsheet.",
            "enum": [
              "api",
              "csv",
              "xlsx",
              "mcp"
            ],
            "type": "string"
          }
        },
        "required": [
          "organization_id",
          "project_id",
          "name"
        ],
        "type": "object"
      },
      "ContactListUpdate": {
        "additionalProperties": false,
        "properties": {
          "name": {
            "maxLength": 120,
            "minLength": 1,
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "ContactRow": {
        "properties": {
          "consent_basis": {
            "enum": [
              "existing_customer",
              "explicit_consent"
            ],
            "type": "string"
          },
          "external_reference": {
            "maxLength": 256,
            "type": "string"
          },
          "first_name": {
            "description": "The contact's first name, as supplied (never derived from `name`); no `{` or `}`. Empty clears it. A custom value named `first_name` in `variables` is a separate value.",
            "maxLength": 80,
            "type": "string"
          },
          "language": {
            "maxLength": 12,
            "type": "string"
          },
          "name": {
            "maxLength": 160,
            "type": "string"
          },
          "phone_e164": {
            "type": "string"
          },
          "time_zone": {
            "description": "IANA time zone such as America/New_York, matched ignoring case. Another spelling of a zone (US/Eastern, Europe/Kiev, EST) is stored as the zone it names; an offset such as UTC-5 is refused. Empty: the number's zone, when the number has only one",
            "type": "string"
          },
          "variables": {
            "description": "Flat template variables: the attempt's whole call context, at most 32 keys and 4 KB, lists of at most 16 items; never secrets",
            "type": "object"
          }
        },
        "required": [
          "phone_e164"
        ],
        "type": "object"
      },
      "ContactUpdate": {
        "properties": {
          "consent_basis": {
            "description": "Why this contact may be called: existing_customer or explicit_consent. Empty clears it.",
            "enum": [
              "existing_customer",
              "explicit_consent",
              ""
            ],
            "type": "string"
          },
          "external_reference": {
            "maxLength": 256,
            "type": "string"
          },
          "first_name": {
            "description": "The contact's first name, as supplied (never derived from `name`); no `{` or `}`. Empty clears it.",
            "maxLength": 80,
            "type": "string"
          },
          "language": {
            "maxLength": 12,
            "type": "string"
          },
          "name": {
            "maxLength": 160,
            "type": "string"
          },
          "phone_e164": {
            "type": "string"
          },
          "time_zone": {
            "description": "IANA time zone such as America/New_York, matched ignoring case. Another spelling of a zone (US/Eastern, Europe/Kiev, EST) is stored as the zone it names; an offset such as UTC-5 is refused. Empty: the number's zone, when the number has only one",
            "type": "string"
          },
          "variables": {
            "description": "Replaces the contact's custom values; the same limits as an import",
            "type": "object"
          }
        },
        "type": "object"
      },
      "ConversationSummary": {
        "properties": {
          "id": {
            "type": "string"
          },
          "last_message": {
            "description": "The conversation's newest message, as GET /v1/messages returns it; absent when it has none.",
            "type": "object"
          },
          "message_count": {
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "id",
          "message_count"
        ],
        "type": "object"
      },
      "CreateOrganization": {
        "properties": {
          "name": {
            "type": "string"
          },
          "project_name": {
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "CustomerOnboarding": {
        "properties": {
          "acceptable_use_attested": {
            "type": "boolean"
          },
          "anticipated_usage": {
            "enum": [
              "business",
              "wholesale",
              "unknown"
            ],
            "type": "string"
          },
          "business_country": {
            "type": "string"
          },
          "business_name": {
            "type": "string"
          },
          "business_website": {
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "service_usage_description": {
            "type": "string"
          },
          "target_regions": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "organization_id",
          "business_name",
          "anticipated_usage",
          "service_usage_description"
        ],
        "type": "object"
      },
      "DashboardBootstrap": {
        "properties": {
          "auth_provider": {
            "type": "string"
          },
          "auth_subject": {
            "type": "string"
          },
          "email": {
            "format": "email",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "organization_name": {
            "type": "string"
          },
          "project_name": {
            "type": "string"
          }
        },
        "required": [
          "email"
        ],
        "type": "object"
      },
      "DoNotCallAdd": {
        "properties": {
          "organization_id": {
            "type": "string"
          },
          "phone_numbers": {
            "items": {
              "type": "string"
            },
            "maxItems": 10000,
            "minItems": 1,
            "type": "array"
          },
          "reason": {
            "maxLength": 160,
            "type": "string"
          },
          "source": {
            "description": "How the numbers were added, shown on the register: api (the default), csv, mcp, or dashboard for the Contacts page.",
            "type": "string"
          }
        },
        "required": [
          "organization_id",
          "phone_numbers"
        ],
        "type": "object"
      },
      "GatewayDeployment": {
        "properties": {
          "carrier_account_id": {
            "type": "string"
          },
          "deployment_model_id": {
            "enum": [
              "fully_managed_cloud",
              "managed_numbers_self_hosted_gateway"
            ],
            "type": "string"
          },
          "mode": {
            "enum": [
              "cloud_hosted",
              "self_hosted"
            ],
            "type": "string"
          },
          "organization_id": {
            "description": "Optional. Defaults to the authenticated principal's own organization. When supplied it must match it; another workspace is refused.",
            "type": "string"
          },
          "plan_id": {
            "type": "string"
          },
          "project_id": {
            "description": "Optional. Defaults to the organization's first project. When supplied it must belong to that organization.",
            "type": "string"
          },
          "provider_account_id": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "GatewayHealthReport": {
        "properties": {
          "api_health": {
            "type": "object"
          },
          "checks": {
            "items": {
              "type": "object"
            },
            "type": "array"
          },
          "last_test_result": {
            "type": "object"
          },
          "realtime_worker_health": {
            "type": "object"
          },
          "secrets_redacted": {
            "type": "boolean"
          },
          "storage": {
            "type": "object"
          },
          "version": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "GatewayOutboundCall": {
        "properties": {
          "agent_endpoint_id": {
            "type": "string"
          },
          "agent_id": {
            "description": "Defaults to the caller number's own configured agent.",
            "type": "string"
          },
          "call_context": {
            "type": "object"
          },
          "external_group_reference": {
            "type": "string"
          },
          "external_reference": {
            "type": "string"
          },
          "from_number_id": {
            "description": "Caller ID by number id; must be an active number of the deployment's project.",
            "type": "string"
          },
          "from_phone_number": {
            "description": "Caller ID in E.164, resolved inside the deployment's own project.",
            "type": "string"
          },
          "idempotency_key": {
            "type": "string"
          },
          "purpose": {
            "type": "string"
          },
          "recipient_type": {
            "type": "string"
          },
          "recording_enabled": {
            "type": "boolean"
          },
          "relationship": {
            "type": "string"
          },
          "to": {
            "description": "Destination in E.164.",
            "type": "string"
          },
          "user_authorization": {
            "type": "object"
          }
        },
        "required": [
          "to"
        ],
        "type": "object"
      },
      "GatewayOutboundExecutionMode": {
        "properties": {
          "outbound_execution_mode": {
            "enum": [
              "queue",
              "local"
            ],
            "type": "string"
          }
        },
        "required": [
          "outbound_execution_mode"
        ],
        "type": "object"
      },
      "HeadlessPayment": {
        "additionalProperties": false,
        "properties": {
          "amount_minor": {
            "description": "Top-up in the wallet currency's minor units; current server top-up bounds apply.",
            "minimum": 1,
            "type": "integer"
          },
          "currency": {
            "description": "Exact owner-approved currency; must match the workspace wallet reported by account_overview.",
            "pattern": "^[A-Z]{3}$",
            "type": "string"
          },
          "idempotency_key": {
            "description": "Opaque key unique to this purchase. Retry the same key and parameters after uncertain outcomes; never include payment credentials.",
            "maxLength": 128,
            "minLength": 1,
            "type": "string"
          },
          "method": {
            "enum": [
              "card_checkout",
              "shared_payment_token"
            ],
            "type": "string"
          },
          "payment_approved": {
            "description": "True only after the owner explicitly approves this amount and currency for purchased Skysay calling credit. Scope consent is not purchase approval. Optional while requesting SPT approval metadata.",
            "type": "boolean"
          },
          "request_challenge": {
            "description": "For SPT, return 402 with amount/currency/Stripe seller profile for a compatible wallet to request owner approval. This is JSON metadata, not MPP, native checkout or MCP elicitation.",
            "type": "boolean"
          },
          "shared_payment_token": {
            "description": "Granted spt_ token approved by the owner for this seller and amount. Omit when requesting the challenge. Never submit card numbers.",
            "maxLength": 1024,
            "type": "string"
          }
        },
        "required": [
          "method",
          "amount_minor",
          "currency",
          "idempotency_key"
        ],
        "type": "object"
      },
      "HeadlessPaymentResult": {
        "description": "Card checkout returns the standard Stripe checkout URL/amount result. SPT returns pending or paid; only a signed matching webhook makes purchased wallet credit available.",
        "properties": {
          "amount_minor": {
            "type": "integer"
          },
          "checkout_url": {
            "format": "uri",
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "next_action": {
            "type": "string"
          },
          "payment_id": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "wallet_credit_timing": {
            "type": "string"
          }
        },
        "type": "object"
      },
      "InboundCallRoute": {
        "properties": {
          "from_phone_number": {
            "description": "Caller number when available.",
            "type": "string"
          },
          "live_media": {
            "description": "Explicitly request the live media worker path. Defaults to false.",
            "type": "boolean"
          },
          "provider": {
            "description": "Carrier/provider that delivered the call.",
            "type": "string"
          },
          "provider_call_id": {
            "description": "Carrier or LiveKit call reference.",
            "type": "string"
          },
          "recording_enabled": {
            "type": "boolean"
          },
          "to_phone_number": {
            "description": "Called platform number assigned to an agent.",
            "type": "string"
          }
        },
        "required": [
          "to_phone_number"
        ],
        "type": "object"
      },
      "IntegrationActionAttachment": {
        "description": "Explicitly attaches one existing Integration Action to this agent. The model cannot choose another action or endpoint.",
        "properties": {
          "connection_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "ref": {
            "description": "Read-only, server-issued. A stable opaque identifier for this configured tool, used to name the tool on a call's transcript rows without storing the tool's own name with them. Ignored if you send it: the value is taken from what is already stored, so it cannot be chosen. It survives any edit that keeps the tool's `name` and `type`; renaming, retyping or removing a tool retires its identifier, and transcript rows recorded before that show the tool's kind instead of a name.",
            "readOnly": true,
            "type": "string"
          },
          "tool_id": {
            "type": "string"
          },
          "type": {
            "const": "connection"
          }
        },
        "required": [
          "type",
          "name",
          "connection_id",
          "tool_id"
        ],
        "type": "object"
      },
      "KnowledgeBase": {
        "properties": {
          "name": {
            "maxLength": 120,
            "minLength": 1,
            "type": "string"
          },
          "organization_id": {
            "description": "Must match the bearer workspace.",
            "type": "string"
          },
          "project_id": {
            "description": "Must belong to the bearer workspace.",
            "type": "string"
          }
        },
        "required": [
          "organization_id",
          "project_id",
          "name"
        ],
        "type": "object"
      },
      "KnowledgeBaseAttachments": {
        "properties": {
          "knowledge_base_ids": {
            "description": "Active bases in the same organization and project only.",
            "items": {
              "type": "string"
            },
            "maxItems": 20,
            "type": "array",
            "uniqueItems": true
          },
          "organization_id": {
            "minLength": 1,
            "type": "string"
          },
          "project_id": {
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "organization_id",
          "project_id",
          "knowledge_base_ids"
        ],
        "type": "object"
      },
      "KnowledgeBaseTest": {
        "additionalProperties": false,
        "properties": {
          "organization_id": {
            "description": "Must match the bearer workspace.",
            "type": "string"
          },
          "project_id": {
            "description": "Must belong to the bearer workspace.",
            "type": "string"
          },
          "question": {
            "maxLength": 1000,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "organization_id",
          "project_id",
          "question"
        ],
        "type": "object"
      },
      "KnowledgeDocumentCommand": {
        "properties": {
          "organization_id": {
            "type": "string"
          },
          "project_id": {
            "type": "string"
          }
        },
        "required": [
          "organization_id",
          "project_id"
        ],
        "type": "object"
      },
      "ListPagination": {
        "additionalProperties": false,
        "properties": {
          "has_more": {
            "description": "True while older rows remain. A page is the whole list only when this is false.",
            "type": "boolean"
          },
          "limit": {
            "description": "The page size used.",
            "minimum": 1,
            "type": "integer"
          },
          "next_cursor": {
            "description": "Send as `cursor` to read the next page; null on the last page.",
            "type": [
              "string",
              "null"
            ]
          },
          "returned": {
            "description": "Items on this page.",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "limit",
          "returned",
          "has_more",
          "next_cursor"
        ],
        "type": "object"
      },
      "ManagedNumberRequest": {
        "properties": {
          "agent_endpoint_id": {
            "description": "Backwards-compatible alias for external audio agents.",
            "type": "string"
          },
          "agent_id": {
            "description": "The AI agent the number should answer with once it is active. Optional: when omitted, an activated number answers with the workspace's only active AI agent, and with nobody (an explicit do-not-answer rule) when there are several or none. A named agent that is no longer active is not replaced.",
            "type": "string"
          },
          "carrier": {
            "description": "Plain alias for provider; defaults to the platform's managed carrier.",
            "type": "string"
          },
          "country": {
            "description": "ISO country code or country name.",
            "type": "string"
          },
          "customer_reference": {
            "type": "string"
          },
          "customer_type": {
            "description": "business, individual, agent_builder, or internal.",
            "type": "string"
          },
          "number_type": {
            "description": "local, national, mobile, toll_free, shared_cost, or UIFN.",
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "preferred_phone_number": {
            "description": "Optional customer preference. Until the carrier enables exact-number search, this does not reserve or promise a specific DID.",
            "type": "string"
          },
          "project_id": {
            "type": "string"
          },
          "requested_capabilities": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "organization_id",
          "project_id",
          "country"
        ],
        "type": "object"
      },
      "NumberRules": {
        "properties": {
          "incoming": {
            "properties": {
              "agent_endpoint_id": {
                "type": "string"
              },
              "agent_id": {
                "type": "string"
              },
              "fallback": {
                "description": "Stored with the rule but not yet applied to calls: a call the number cannot take is not forwarded to the fallback today.",
                "type": "object"
              },
              "phone_number": {
                "description": "Forward target when forwarding to another phone number.",
                "type": "string"
              },
              "phone_server_id": {
                "type": "string"
              },
              "when_called": {
                "enum": [
                  "answer_with_skysay_agent",
                  "send_live_audio_to_my_app",
                  "send_to_my_phone_server",
                  "forward_to_phone_number",
                  "disabled"
                ],
                "type": "string"
              }
            },
            "type": "object"
          },
          "outgoing": {
            "properties": {
              "allowed_agents": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "allowed_countries": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "can_be_used_as_caller_id": {
                "type": "boolean"
              },
              "max_concurrent_calls": {
                "description": "0 means no explicit Number rule cap.",
                "type": "integer"
              }
            },
            "type": "object"
          },
          "recording": {
            "properties": {
              "consent_state": {
                "type": "string"
              },
              "enabled": {
                "type": "boolean"
              },
              "retention_days": {
                "type": "integer"
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "OffsetPage": {
        "properties": {
          "has_more": {
            "type": "boolean"
          },
          "limit": {
            "minimum": 1,
            "type": "integer"
          },
          "next_offset": {
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "offset": {
            "minimum": 0,
            "type": "integer"
          },
          "returned_count": {
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "limit",
          "offset",
          "returned_count",
          "has_more",
          "next_offset"
        ],
        "type": "object"
      },
      "OutboundCall": {
        "description": "recipient_type is optional and recorded, never enforced. Destination country is authorized against the org's active workspace capabilities (authorize_outbound), not a fixed platform allowlist. Blocked categories mirror carrier acceptable-use policy; debt collection is not a blocked category -- payment_reminder (first-party or third-party) is permitted with per-call authorization, subject to per-customer onboarding approval and the acceptable-use policy.",
        "properties": {
          "agent_endpoint_id": {
            "description": "Backwards-compatible alias for the selected agent endpoint; new integrations should send agent_id.",
            "type": "string"
          },
          "agent_id": {
            "description": "Canonical saved agent selection. It must belong to the authenticated organization and selected project; the call starts from its stored voice stack.",
            "type": "string"
          },
          "call_context": {
            "description": "Small bounded, non-secret facts valid at call start: at most 32 keys and 4 KB, lists of at most 16 items. Never put endpoint credentials, mutable case history, or a requested voice-stack override here. A conversation workflow read tool is served from this object: put its declared projection fields under case_snapshot, or as top-level keys; every declared field must be present and non-empty or the workflow takes its case-unavailable path. `navigation_hint` is reserved: plain text of at most 500 characters telling the agent what the phone menu it will meet expects, for example \"Press 2 for English, then 1 for billing.\"; it reaches the agent as the built-in variable `call.navigation_hint`, and a longer or non-text hint refuses the call with `call_variables_invalid`. A variable you declared as a keypad field (`keypad` on the field, see PUT /v1/agents/{agent_id}/variables) is read from here too, but only to be keyed on a phone menu: it is never given to the agent as text, and a call whose texts name the field or carry its digits is refused.",
            "maxProperties": 32,
            "type": "object"
          },
          "country": {
            "description": "Optional. The destination country is derived from to_phone_number's calling code; a value sent here is used only when the destination cannot be attributed to a country, and never overrides it. Authorized per-organization by the active workspace capabilities.",
            "type": "string"
          },
          "external_group_reference": {
            "description": "Optional customer-owned batch/group reference.",
            "maxLength": 256,
            "type": "string"
          },
          "external_reference": {
            "description": "Customer-owned stable case/reference ID. Skysay stores it but does not interpret it.",
            "maxLength": 256,
            "type": "string"
          },
          "first_message": {
            "description": "Compatibility alias for runtime_config.first_message.",
            "maxLength": 1000,
            "type": "string"
          },
          "from_phone_number_id": {
            "type": "string"
          },
          "idempotency_key": {
            "description": "Optional body alternative to the Idempotency-Key header. Retries must reuse the same value with the same request.",
            "maxLength": 200,
            "type": "string"
          },
          "instructions": {
            "description": "Compatibility alias for runtime_config.instructions.",
            "maxLength": 8000,
            "type": "string"
          },
          "language": {
            "description": "Compatibility alias for runtime_config.language. When both are supplied, runtime_config.language takes precedence. A selected multilingual hosted language chooses one allowed published profile before reservation. An engine whose agent-catalog row says has_language_axis false (a voice-to-voice engine) publishes no profile per locale; it accepts any language the platform catalogue runs and speaks the call in it.",
            "maxLength": 35,
            "minLength": 2,
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "project_id": {
            "type": "string"
          },
          "purpose": {
            "description": "Required, free-form outbound purpose declaration -- a non-empty purpose is the compliance paper trail; a novel purpose token is never refused for being novel. Documented catalog (pick one or send your own token): appointment, booking_confirmation, callback_requested, customer_outreach, delivery_notification, ops_alert, other, payment_reminder, personal_errand, reservation, status_check, support.",
            "type": "string"
          },
          "recipient_type": {
            "description": "Optional, never enforced or required -- outbound is not restricted to business recipients. Persisted verbatim on the call row if sent; SMS has no recipient_type column, so it is accepted but not stored there.",
            "type": "string"
          },
          "runtime_config": {
            "$ref": "#/components/schemas/OutboundCallRuntimeConfig"
          },
          "to_phone_number": {
            "type": "string"
          },
          "user_authorization": {
            "type": "object"
          }
        },
        "required": [
          "organization_id",
          "project_id",
          "from_phone_number_id",
          "to_phone_number",
          "purpose"
        ],
        "type": "object"
      },
      "OutboundCallRuntimeConfig": {
        "additionalProperties": false,
        "description": "Bounded non-secret, per-call instructions. Clients cannot supply a frozen profile, credential ID, provider, model, or voice selection. runtime_config.language takes precedence over the top-level compatibility alias. For a multilingual hosted agent, language selects an allowed published profile before reserve and worker dispatch. An engine whose agent-catalog row says has_language_axis false (a voice-to-voice engine) publishes no profile per locale; it accepts any language the platform catalogue runs and speaks the call in it.",
        "properties": {
          "first_message": {
            "maxLength": 1000,
            "type": "string"
          },
          "instructions": {
            "maxLength": 8000,
            "type": "string"
          },
          "language": {
            "maxLength": 35,
            "minLength": 2,
            "type": "string"
          }
        },
        "type": "object"
      },
      "PhoneNumberRequest": {
        "properties": {
          "agent_endpoint_id": {
            "type": "string"
          },
          "available_provider_number_id": {
            "description": "Provider available-number identifier captured before ordering (the carrier's available_dids.id).",
            "type": "string"
          },
          "carrier": {
            "description": "Plain alias for provider.",
            "type": "string"
          },
          "carrier_number_reference": {
            "description": "Plain alias for provider_number_id.",
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "monthly_recurring_minor": {
            "type": "integer"
          },
          "number_type": {
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "phone_number": {
            "description": "Plain alias for e164.",
            "type": "string"
          },
          "price_snapshot": {
            "description": "Captured quote used for audit/customer display; the carrier API remains authoritative when quote is fetched.",
            "type": "object"
          },
          "project_id": {
            "type": "string"
          },
          "provider_order_id": {
            "description": "Provider order identifier returned after order execution.",
            "type": "string"
          },
          "setup_cost_minor": {
            "type": "integer"
          }
        },
        "required": [
          "organization_id",
          "project_id"
        ],
        "type": "object"
      },
      "PhoneServer": {
        "properties": {
          "allowed_sip_ips": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "auth_mode": {
            "enum": [
              "ip_allowlist",
              "sip_credentials",
              "ip_allowlist_and_credentials"
            ],
            "type": "string"
          },
          "metadata": {
            "type": "object"
          },
          "name": {
            "description": "Customer-facing label, e.g. My phone server.",
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "password_reference": {
            "description": "Reference to an external secret; plaintext passwords are not returned.",
            "type": "string"
          },
          "port": {
            "default": 5060,
            "type": "integer"
          },
          "project_id": {
            "type": "string"
          },
          "server_address": {
            "description": "Hostname or public IP of the customer's phone server.",
            "type": "string"
          },
          "transport": {
            "enum": [
              "udp",
              "tcp",
              "tls"
            ],
            "type": "string"
          },
          "username": {
            "type": "string"
          }
        },
        "required": [
          "organization_id",
          "server_address"
        ],
        "type": "object"
      },
      "PostCallAnalysis": {
        "description": "Bounded generic asynchronous analysis; customer policy must not treat it as an authoritative business outcome without its own validation.",
        "properties": {
          "advisory": {
            "const": true,
            "type": "boolean"
          },
          "analysis_version": {
            "description": "Immutable platform analysis implementation version snapshotted when this extraction is enqueued; distinct from schema_version and never relabeled by a later platform release.",
            "minimum": 1,
            "type": "integer"
          },
          "completed_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "evidence": {
            "$ref": "#/components/schemas/PostCallAnalysisEvidence"
          },
          "failure_reason": {
            "type": "string"
          },
          "quality_flags": {
            "items": {
              "enum": [
                "recording_unavailable",
                "transcript_partial",
                "transcript_unavailable",
                "extraction_failed",
                "evidence_unavailable"
              ],
              "type": "string"
            },
            "type": "array",
            "uniqueItems": true
          },
          "result": {
            "description": "Bounded schema-validated scalar output; strings may be truncated on group reads.",
            "type": "object"
          },
          "result_truncated": {
            "type": "boolean"
          },
          "schema_version": {
            "minimum": 0,
            "type": "integer"
          },
          "status": {
            "enum": [
              "not_configured",
              "pending",
              "ready",
              "failed"
            ],
            "type": "string"
          }
        },
        "required": [
          "analysis_version",
          "advisory",
          "status",
          "schema_version",
          "evidence",
          "quality_flags"
        ],
        "type": "object"
      },
      "PostCallAnalysisEvidence": {
        "properties": {
          "anchors": {
            "items": {
              "$ref": "#/components/schemas/PostCallEvidenceAnchor"
            },
            "maxItems": 24,
            "type": "array"
          },
          "availability": {
            "description": "Whether this response could assemble the bounded evidence projection. unavailable makes no claim that the underlying recording or transcript is absent.",
            "enum": [
              "available",
              "unavailable"
            ],
            "type": "string"
          },
          "recording": {
            "properties": {
              "available": {
                "type": "boolean"
              },
              "duration_seconds": {
                "minimum": 0,
                "type": "number"
              },
              "kind": {
                "type": "string"
              },
              "recording_id": {
                "type": "string"
              },
              "url": {
                "description": "Authenticated relative Skysay recording route when available.",
                "type": "string"
              }
            },
            "type": "object"
          },
          "transcript": {
            "properties": {
              "available": {
                "type": "boolean"
              },
              "transcript_status": {
                "type": "string"
              },
              "turn_count": {
                "minimum": 0,
                "type": "integer"
              },
              "url": {
                "description": "Authenticated relative Skysay transcript route when available.",
                "type": "string"
              }
            },
            "type": "object"
          }
        },
        "required": [
          "availability",
          "recording",
          "transcript",
          "anchors"
        ],
        "type": "object"
      },
      "PostCallEvidenceAnchor": {
        "description": "Authorized pointer into an authenticated call transcript/recording. It never contains transcript text or an audio URL.",
        "properties": {
          "end_ms": {
            "minimum": 0,
            "type": "integer"
          },
          "sequence": {
            "minimum": 0,
            "type": "integer"
          },
          "speaker": {
            "type": "string"
          },
          "start_ms": {
            "minimum": 0,
            "type": "integer"
          },
          "turn_id": {
            "type": "string"
          }
        },
        "required": [
          "turn_id",
          "speaker",
          "sequence",
          "start_ms",
          "end_ms"
        ],
        "type": "object"
      },
      "PostCallExtractionConfiguration": {
        "properties": {
          "enabled": {
            "default": true,
            "type": "boolean"
          },
          "schema": {
            "description": "When enabled: an object schema with 1-24 scalar properties (string, number, integer, or boolean), optional string enums, and no additional properties.",
            "type": "object"
          }
        },
        "type": "object"
      },
      "PostCallExtractionResult": {
        "properties": {
          "advisory": {
            "const": true,
            "description": "Extraction output is advisory and never changes call state, billing, or policy.",
            "type": "boolean"
          },
          "agent_id": {
            "type": "string"
          },
          "analysis_version": {
            "description": "Immutable platform analysis implementation version snapshotted when this extraction is enqueued; distinct from schema_version and never relabeled by a later platform release.",
            "minimum": 1,
            "type": "integer"
          },
          "authoritative_call": {
            "$ref": "#/components/schemas/CallResultCallReference"
          },
          "call_id": {
            "type": "string"
          },
          "completed_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "error_code": {
            "description": "Safe, non-provider-specific processing outcome.",
            "type": "string"
          },
          "evidence": {
            "$ref": "#/components/schemas/PostCallAnalysisEvidence"
          },
          "failure_reason": {
            "description": "Bounded safe failure reason; aliases error_code for new consumers.",
            "type": "string"
          },
          "quality_flags": {
            "description": "Observable generic platform-quality facts, not a business disposition.",
            "items": {
              "enum": [
                "recording_unavailable",
                "transcript_partial",
                "transcript_unavailable",
                "extraction_failed",
                "evidence_unavailable"
              ],
              "type": "string"
            },
            "type": "array",
            "uniqueItems": true
          },
          "result": {
            "description": "Schema-validated advisory scalar output. Values can be null when unavailable.",
            "type": "object"
          },
          "schema_version": {
            "type": "integer"
          },
          "status": {
            "enum": [
              "not_configured",
              "pending",
              "running",
              "ready",
              "failed"
            ],
            "type": "string"
          }
        },
        "required": [
          "call_id",
          "status"
        ],
        "type": "object"
      },
      "RetryRule": {
        "properties": {
          "backoff_seconds": {
            "items": {
              "maximum": 2592000,
              "minimum": 60,
              "type": "integer"
            },
            "type": "array"
          },
          "max_attempts": {
            "maximum": 10,
            "minimum": 1,
            "type": "integer"
          },
          "retry": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "RouteTrialCall": {
        "properties": {
          "from_phone_number": {
            "type": "string"
          },
          "live_media": {
            "description": "Explicitly request the live media worker path.",
            "type": "boolean"
          },
          "pin": {
            "type": "string"
          },
          "provider_call_id": {
            "type": "string"
          }
        },
        "required": [
          "pin"
        ],
        "type": "object"
      },
      "SmsMessage": {
        "description": "recipient_type is optional and never enforced; there is no recipient_type column on the SMS row, so it is accepted but not persisted. Blocked categories mirror carrier acceptable-use policy; debt collection is not a blocked category -- payment_reminder (first-party or third-party) is permitted with per-call authorization, subject to per-customer onboarding approval and the acceptable-use policy.",
        "properties": {
          "body": {
            "type": "string"
          },
          "categories": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "from_phone_number_id": {
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "project_id": {
            "type": "string"
          },
          "purpose": {
            "description": "Required, free-form outbound purpose declaration -- a non-empty purpose is the compliance paper trail; a novel purpose token is never refused for being novel. Documented catalog (pick one or send your own token): appointment, booking_confirmation, callback_requested, customer_outreach, delivery_notification, ops_alert, other, payment_reminder, personal_errand, reservation, status_check, support.",
            "type": "string"
          },
          "recipient_type": {
            "description": "Optional, never enforced or required -- outbound is not restricted to business recipients. Persisted verbatim on the call row if sent; SMS has no recipient_type column, so it is accepted but not stored there.",
            "type": "string"
          },
          "to_phone_number": {
            "type": "string"
          },
          "user_authorization": {
            "type": "object"
          }
        },
        "required": [
          "organization_id",
          "project_id",
          "from_phone_number_id",
          "to_phone_number",
          "body",
          "purpose"
        ],
        "type": "object"
      },
      "StripeTopup": {
        "anyOf": [
          {
            "required": [
              "amount_minor"
            ]
          },
          {
            "required": [
              "amount_cents"
            ]
          }
        ],
        "properties": {
          "amount_cents": {
            "minimum": 1,
            "type": "integer"
          },
          "amount_minor": {
            "minimum": 1,
            "type": "integer"
          },
          "cancel_url": {
            "format": "uri",
            "type": "string"
          },
          "currency": {
            "description": "Must match wallet settlement currency when provided.",
            "type": "string"
          },
          "idempotency_key": {
            "description": "Request-aware checkout uses the persisted claim even when a caller supplies a key.",
            "type": "string"
          },
          "number_request_id": {
            "description": "Optional exact request context; never confirms, purchases or reserves a number.",
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "project_id": {
            "type": "string"
          },
          "receipt_email": {
            "format": "email",
            "type": "string"
          },
          "success_url": {
            "format": "uri",
            "type": "string"
          }
        },
        "required": [
          "organization_id"
        ],
        "type": "object"
      },
      "TechnicalCallFailureEvidence": {
        "additionalProperties": false,
        "allOf": [
          {
            "if": {
              "properties": {
                "terminal_status": {
                  "const": "no_answer"
                }
              },
              "required": [
                "terminal_status"
              ]
            },
            "then": {
              "properties": {
                "failure_reason_code": {
                  "const": "outbound_ringing_timeout"
                }
              },
              "required": [
                "failure_reason_code"
              ]
            }
          }
        ],
        "description": "Versioned, allowlisted technical failure evidence for future calls. It never contains exception text, provider payloads, SIP headers, phone numbers, transcripts, or secrets. Historical or unclassified failures remain unknown; no_answer requires the durable outbound_ringing_timeout reason and is never inferred from duration.",
        "properties": {
          "availability": {
            "enum": [
              "available",
              "unknown",
              "not_applicable"
            ],
            "type": "string"
          },
          "evidence_version": {
            "const": 1,
            "type": "integer"
          },
          "failure_reason_code": {
            "enum": [
              "carrier_rate_missing",
              "inbound_admission_rejected",
              "inbound_capacity_reached",
              "unknown",
              "outbound_ringing_timeout",
              "outbound_authorization_rejected",
              "outbound_authorization_held_for_review",
              "outbound_media_admission_rejected",
              "outbound_trunk_rejected",
              "outbound_room_rejected",
              "outbound_dispatch_rejected",
              "outbound_sip_participant_rejected",
              "outbound_pre_media_rejected",
              "originate_worker_job_failed",
              "outbound_capacity_unavailable",
              "local_origination_not_started",
              "policy_blocked",
              "canceled_before_origination",
              "originate_worker_job_stale",
              "worker_entry_failed",
              "component_configuration_rejected",
              "voice_id_missing",
              "tts_model_missing",
              "llm_model_missing",
              "system_prompt_missing"
            ],
            "type": "string"
          },
          "failure_stage": {
            "enum": [
              "inbound_admission",
              "unknown",
              "outbound_authorization",
              "outbound_media_admission",
              "outbound_trunk",
              "outbound_room",
              "outbound_dispatch",
              "outbound_sip_participant",
              "outbound_pre_media",
              "outbound_worker",
              "outbound_local",
              "outbound_policy",
              "outbound_queue",
              "worker_entry",
              "component_configuration"
            ],
            "type": "string"
          },
          "sip_status_code": {
            "maximum": 699,
            "minimum": 200,
            "type": [
              "integer",
              "null"
            ]
          },
          "terminal_status": {
            "enum": [
              "completed",
              "failed",
              "busy",
              "no_answer",
              "canceled",
              "unknown"
            ],
            "type": "string"
          }
        },
        "required": [
          "evidence_version",
          "availability",
          "terminal_status",
          "failure_reason_code",
          "failure_stage",
          "sip_status_code"
        ],
        "type": "object"
      },
      "TranscriptPagination": {
        "properties": {
          "has_more": {
            "type": "boolean"
          },
          "limit": {
            "maximum": 500,
            "minimum": 1,
            "type": "integer"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          },
          "returned": {
            "maximum": 500,
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "limit",
          "returned",
          "has_more",
          "next_cursor"
        ],
        "type": "object"
      },
      "TranscriptTimelineItem": {
        "oneOf": [
          {
            "properties": {
              "data": {
                "$ref": "#/components/schemas/TranscriptTurn"
              },
              "type": {
                "const": "turn"
              }
            },
            "required": [
              "type",
              "data"
            ],
            "type": "object"
          },
          {
            "properties": {
              "data": {
                "properties": {
                  "speaker": {
                    "const": "caller",
                    "type": "string"
                  },
                  "start_ms": {
                    "type": "integer"
                  }
                },
                "required": [
                  "speaker",
                  "start_ms"
                ],
                "type": "object"
              },
              "type": {
                "const": "caller_spoke_no_transcript"
              }
            },
            "required": [
              "type",
              "data"
            ],
            "type": "object"
          }
        ]
      },
      "TranscriptTurn": {
        "properties": {
          "audio_artifact_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "call_id": {
            "type": "string"
          },
          "confidence": {
            "type": [
              "number",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "end_ms": {
            "type": [
              "integer",
              "null"
            ]
          },
          "id": {
            "type": "string"
          },
          "metadata": {
            "type": "object"
          },
          "organization_id": {
            "type": "string"
          },
          "project_id": {
            "type": "string"
          },
          "sequence": {
            "type": "integer"
          },
          "speaker": {
            "type": "string"
          },
          "start_ms": {
            "type": [
              "integer",
              "null"
            ]
          },
          "text": {
            "description": "Original text; null with unavailable_reason for an oversized unit.",
            "type": [
              "string",
              "null"
            ]
          },
          "translated_text": {
            "description": "Cached text for this exact original ID and revision; null when unavailable.",
            "type": [
              "string",
              "null"
            ]
          },
          "translation_status": {
            "enum": [
              "not_requested",
              "queued",
              "running",
              "ready",
              "waiting_funds",
              "failed",
              "stale"
            ],
            "type": "string"
          },
          "unavailable_reason": {
            "enum": [
              "source_unit_too_large"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "speaker",
          "sequence",
          "text",
          "metadata"
        ],
        "type": "object"
      },
      "TranslationPolicy": {
        "additionalProperties": false,
        "properties": {
          "enabled": {
            "default": false,
            "type": "boolean"
          },
          "target_languages": {
            "items": {
              "enum": [
                "ar",
                "bn",
                "bg",
                "ca",
                "zh",
                "hr",
                "cs",
                "da",
                "nl",
                "en",
                "et",
                "fi",
                "fr",
                "de",
                "el",
                "gu",
                "he",
                "hi",
                "hu",
                "id",
                "it",
                "ja",
                "kn",
                "ko",
                "lv",
                "lt",
                "ms",
                "ml",
                "mr",
                "no",
                "fa",
                "pl",
                "pt",
                "ro",
                "ru",
                "sr",
                "si",
                "sk",
                "sl",
                "es",
                "sw",
                "sv",
                "ta",
                "te",
                "th",
                "tr",
                "uk",
                "ur",
                "vi"
              ],
              "type": "string"
            },
            "maxItems": 6,
            "type": "array",
            "uniqueItems": true
          }
        },
        "type": "object"
      },
      "TranslationRequest": {
        "additionalProperties": false,
        "description": "Idempotent for the current source revision and target. Repeating the command reuses the saved result; no client-generated idempotency key is needed.",
        "properties": {
          "target_language": {
            "enum": [
              "ar",
              "bn",
              "bg",
              "ca",
              "zh",
              "hr",
              "cs",
              "da",
              "nl",
              "en",
              "et",
              "fi",
              "fr",
              "de",
              "el",
              "gu",
              "he",
              "hi",
              "hu",
              "id",
              "it",
              "ja",
              "kn",
              "ko",
              "lv",
              "lt",
              "ms",
              "ml",
              "mr",
              "no",
              "fa",
              "pl",
              "pt",
              "ro",
              "ru",
              "sr",
              "si",
              "sk",
              "sl",
              "es",
              "sw",
              "sv",
              "ta",
              "te",
              "th",
              "tr",
              "uk",
              "ur",
              "vi"
            ],
            "type": "string"
          }
        },
        "required": [
          "target_language"
        ],
        "type": "object"
      },
      "TrialCall": {
        "properties": {
          "agent_endpoint_id": {
            "type": "string"
          },
          "agent_id": {
            "type": "string"
          },
          "demo_phone_number": {
            "type": "string"
          },
          "max_free_seconds": {
            "type": "integer"
          },
          "organization_id": {
            "type": "string"
          },
          "project_id": {
            "type": "string"
          },
          "synthetic_only": {
            "description": "Offline fixture only, never live PSTN.",
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "organization_id",
          "project_id",
          "synthetic_only"
        ],
        "type": "object"
      },
      "VariablesConfig": {
        "additionalProperties": false,
        "description": "An agent's call variables, SMS templates and caller lookup. Exported from config_schema.py (the API's copy; tests pin the two equal) for the dashboard and the docs. tests/fixtures/call_variables/config_cases.json is the accept/reject contract.",
        "properties": {
          "caller_lookup": {
            "default": null,
            "oneOf": [
              {
                "type": "null"
              },
              {
                "additionalProperties": false,
                "properties": {
                  "contact_list_id": {
                    "maxLength": 120,
                    "minLength": 1,
                    "type": "string"
                  }
                },
                "required": [
                  "contact_list_id"
                ],
                "type": "object"
              }
            ]
          },
          "sms_mode": {
            "default": "free_text",
            "enum": [
              "free_text",
              "templates_only"
            ]
          },
          "sms_templates": {
            "additionalProperties": {
              "$ref": "#/components/schemas/VariablesConfig_sms_template"
            },
            "default": {},
            "maxProperties": 20,
            "propertyNames": {
              "pattern": "^[a-z0-9_]{1,40}$"
            },
            "type": "object"
          },
          "variables": {
            "$ref": "#/components/schemas/VariablesConfig_variables"
          }
        },
        "title": "VariablesConfig",
        "type": "object"
      },
      "VariablesConfig_field": {
        "additionalProperties": false,
        "properties": {
          "before_confirmation": {
            "type": "boolean"
          },
          "format": {
            "enum": [
              "identifier",
              "date:YYYY-MM-DD",
              "date:DD/MM/YYYY",
              "date:MM/DD/YYYY"
            ]
          },
          "item": {
            "additionalProperties": {
              "enum": [
                "string",
                "number",
                "boolean"
              ]
            },
            "minProperties": 1,
            "propertyNames": {
              "$ref": "#/components/schemas/VariablesConfig_name"
            },
            "type": "object"
          },
          "keypad": {
            "additionalProperties": false,
            "description": "The call may enter this value on a phone menu keypad. It is never given to the model or any prompt, only keyed through the keypad.",
            "properties": {
              "class": {
                "enum": [
                  "reference",
                  "card",
                  "pin",
                  "government_id",
                  "date_of_birth",
                  "other"
                ]
              }
            },
            "required": [
              "class"
            ],
            "type": "object"
          },
          "max_items": {
            "maximum": 16,
            "minimum": 1,
            "type": "integer"
          },
          "max_length": {
            "maximum": 512,
            "minimum": 1,
            "type": "integer"
          },
          "protected": {
            "type": "boolean"
          },
          "required": {
            "type": "boolean"
          },
          "requires": {
            "enum": [
              "confirmation",
              "knowledge_match"
            ]
          },
          "type": {
            "enum": [
              "string",
              "number",
              "boolean",
              "list"
            ]
          }
        },
        "required": [
          "type"
        ],
        "type": "object"
      },
      "VariablesConfig_name": {
        "pattern": "^[A-Za-z_][A-Za-z0-9_]{0,79}$",
        "type": "string"
      },
      "VariablesConfig_sms_slot": {
        "additionalProperties": false,
        "properties": {
          "max_length": {
            "default": 120,
            "maximum": 120,
            "minimum": 1,
            "type": "integer"
          },
          "source": {
            "const": "model"
          }
        },
        "required": [
          "source"
        ],
        "type": "object"
      },
      "VariablesConfig_sms_template": {
        "additionalProperties": false,
        "properties": {
          "slots": {
            "additionalProperties": {
              "$ref": "#/components/schemas/VariablesConfig_sms_slot"
            },
            "default": {},
            "maxProperties": 8,
            "propertyNames": {
              "$ref": "#/components/schemas/VariablesConfig_name"
            },
            "type": "object"
          },
          "text": {
            "additionalProperties": {
              "minLength": 1,
              "type": "string"
            },
            "description": "Text per language. The agent's primary language is required; any other language falls back to it.",
            "minProperties": 1,
            "propertyNames": {
              "pattern": "^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$"
            },
            "type": "object"
          }
        },
        "required": [
          "text"
        ],
        "type": "object"
      },
      "VariablesConfig_variables": {
        "additionalProperties": false,
        "default": {
          "fields": {},
          "version": 1
        },
        "properties": {
          "fields": {
            "additionalProperties": {
              "$ref": "#/components/schemas/VariablesConfig_field"
            },
            "maxProperties": 32,
            "propertyNames": {
              "$ref": "#/components/schemas/VariablesConfig_name"
            },
            "type": "object"
          },
          "version": {
            "const": 1
          }
        },
        "required": [
          "version",
          "fields"
        ],
        "type": "object"
      },
      "VoiceComponentSelection": {
        "properties": {
          "language": {
            "description": "STT selection language when supplied.",
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "voice_id": {
            "description": "TTS voice identifier when supplied.",
            "type": "string"
          }
        },
        "required": [
          "provider",
          "model"
        ],
        "type": "object"
      },
      "VoicePreviewRequest": {
        "description": "One short synthesis of a bounded caller sentence for a selected TTS voice. Customer requests bind preview spend to the authenticated workspace; organization_id is an optional consistency hint and a foreign value is rejected.",
        "properties": {
          "agent_id": {
            "description": "Optional saved hosted agent pin for a managed preview only. It must belong to the authenticated project and exactly match the requested platform-managed provider, model, voice id, and language. It permits re-testing a stored selection missing from the current provider listing; it does not authorize a new voice choice or any BYOK selection.",
            "type": "string"
          },
          "audio_environment": {
            "$ref": "#/components/schemas/AudioEnvironmentSelection",
            "description": "Optional immutable ambience selection. When present, the preview WAV is a deterministic headroom-safe local audition only; this does not claim a receiver-side final mix. Provider voice and explicit language are unchanged."
          },
          "credential_id": {
            "description": "Optional selected BYOK credential. It is validated at the provider and then used to render this preview; a platform credential is never a fallback.",
            "type": "string"
          },
          "delivery": {
            "description": "Optional delivery profile, the same value an agent stores in `voice_polish.delivery`. The preview applies exactly the provider settings a call with that profile would use; for a provider/model without a truthful control (Fish Audio, Azure) the audio is unchanged and the response reports `delivery_applied: false`.",
            "enum": [
              "natural",
              "lively",
              "calm",
              "precise"
            ],
            "type": "string"
          },
          "language": {
            "description": "Any language the selected provider advertises.",
            "type": "string"
          },
          "model": {
            "type": "string"
          },
          "organization_id": {
            "description": "Optional consistency hint; if supplied, it must match the bearer workspace.",
            "type": "string"
          },
          "project_id": {
            "description": "Must belong to the bearer workspace.",
            "type": "string"
          },
          "provider": {
            "type": "string"
          },
          "text": {
            "description": "Optional sentence to speak. Omitted or blank falls back to a short product sample only when no audio_environment is supplied. With an audio_environment it is required and must be non-blank, so the requested text is never silently replaced. It is synthesized, never stored, and never echoed in the response.",
            "maxLength": 200,
            "type": "string"
          },
          "voice_id": {
            "description": "Exact provider voice id. On a platform credential it must be present in the browsable voice inventory, except an existing saved hosted agent may explicitly re-test its exact current selection; with a BYOK credential any id the provider accepts is allowed, so cloned and private voices work.",
            "type": "string"
          }
        },
        "required": [
          "project_id",
          "provider",
          "model",
          "voice_id",
          "language"
        ],
        "type": "object"
      },
      "WebCall": {
        "properties": {
          "agent_endpoint_id": {
            "type": "string"
          },
          "agent_id": {
            "type": "string"
          },
          "browser_session_id": {
            "type": "string"
          },
          "organization_id": {
            "type": "string"
          },
          "project_id": {
            "type": "string"
          },
          "purpose": {
            "type": "string"
          },
          "recording_enabled": {
            "type": "boolean"
          }
        },
        "required": [
          "organization_id",
          "project_id"
        ],
        "type": "object"
      },
      "WebhookAgentTool": {
        "properties": {
          "description": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "parameters": {
            "type": "object"
          },
          "ref": {
            "description": "Read-only, server-issued. A stable opaque identifier for this configured tool, used to name the tool on a call's transcript rows without storing the tool's own name with them. Ignored if you send it: the value is taken from what is already stored, so it cannot be chosen. It survives any edit that keeps the tool's `name` and `type`; renaming, retyping or removing a tool retires its identifier, and transcript rows recorded before that show the tool's kind instead of a name.",
            "readOnly": true,
            "type": "string"
          },
          "type": {
            "const": "webhook"
          },
          "url": {
            "format": "uri",
            "type": "string"
          }
        },
        "required": [
          "type",
          "name",
          "url"
        ],
        "type": "object"
      },
      "WorkflowVersionSummary": {
        "description": "Safe immutable version metadata. The separate published field is null before the first explicit publish.",
        "properties": {
          "compiled_hash": {
            "type": "string"
          },
          "migrated_from_version": {
            "description": "The archived version this one was moved from when workflows moved onto call variables; null for a version you published.",
            "nullable": true,
            "type": "integer"
          },
          "published_at": {
            "type": "string"
          },
          "status": {
            "description": "active, superseded, or archived_v2: a version from before call variables, kept under its number and never restored.",
            "type": "string"
          },
          "version": {
            "minimum": 1,
            "type": "integer"
          }
        },
        "type": "object"
      },
      "WorkspaceContext": {
        "properties": {
          "organization": {
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name"
            ],
            "type": "object"
          },
          "project_count": {
            "description": "Exact number of projects in the bearer organization.",
            "minimum": 0,
            "type": "integer"
          },
          "projects": {
            "items": {
              "properties": {
                "environment": {
                  "type": "string"
                },
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "name",
                "environment"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "projects_page": {
            "$ref": "#/components/schemas/OffsetPage"
          }
        },
        "required": [
          "organization",
          "project_count",
          "projects",
          "projects_page"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "ApiKeyBearer": {
        "description": "Scoped sky_ bearer keys are required by default for tenant/operator routes. Set SKYSAY_AUTH_MODE=development_open only for local demos.",
        "scheme": "bearer",
        "type": "http"
      }
    }
  },
  "info": {
    "description": "Agent-native telephony API for customer onboarding, credits, numbers, webhooks, gateways, and policy-gated calls. Request bodies are UTF-8. Send JSON: a body that is absent or whitespace-only reads as an empty object, and a JSON body that is not valid JSON, is not a JSON object, or is not UTF-8 returns 400. (The provider-callback routes that have always accepted application/x-www-form-urlencoded or text/plain still do.) A request carrying any Content-Encoding other than identity returns 415 \u2014 this API does not accept compressed request bodies. A required field that is missing, null or blank returns 400 naming the field.",
    "title": "Skysay API",
    "version": "0.1.0"
  },
  "openapi": "3.1.0",
  "paths": {
    "/mcp": {
      "post": {
        "description": "MCP Streamable HTTP transport with JSON responses. Protocol 2025-11-25, 2025-06-18 or 2025-03-26 through the initialize handshake; protocol 2026-07-28 without a handshake, selected by the MCP-Protocol-Version header (server/discover, per-request _meta, Mcp-Method and Mcp-Name headers). GET and HEAD answer 405 with Allow: POST; no server-sent event stream is offered.",
        "responses": {
          "200": {
            "description": "JSON-RPC response"
          },
          "202": {
            "description": "Notification or client response accepted; empty body"
          },
          "400": {
            "description": "Malformed body, unsupported MCP-Protocol-Version header, or a 2026-07-28 request refused with a JSON-RPC error (-32600, -32602, -32020 header mismatch, -32022 unsupported version)"
          },
          "403": {
            "description": "Invalid supplied Origin or insufficient credential permissions"
          },
          "404": {
            "description": "A 2026-07-28 request for a method this server does not implement (JSON-RPC -32601)"
          }
        },
        "summary": "Hosted MCP endpoint"
      }
    },
    "/mcp/read-only": {
      "post": {
        "description": "Streamable HTTP profile exposing account_overview, list_agents, get_agent, list_numbers and list_calls only, in the same protocol revisions as /mcp. Every other tool is refused before dispatch, regardless of credential permissions. OAuth tokens are audience-bound to this endpoint and cannot be used on /mcp. GET and HEAD return 405.",
        "responses": {
          "200": {
            "description": "JSON-RPC response"
          },
          "202": {
            "description": "Notification accepted"
          },
          "401": {
            "description": "Authentication or resource audience rejected"
          },
          "403": {
            "description": "Invalid supplied Origin or insufficient credential permissions"
          }
        },
        "summary": "Use the read-only MCP connector"
      }
    },
    "/v1/activation-state": {
      "get": {
        "responses": {
          "200": {
            "description": "Activation state"
          }
        },
        "summary": "Get live customer activation checklist and next action"
      }
    },
    "/v1/agent-catalog": {
      "get": {
        "description": "Pass metered_only=true to receive only the launch-supported hosted components with a runtime adapter, native meter, pinned retail rate, and safe credential contract (validated BYOK where supported or an explicit platform-managed provider contract). The payload also includes conversation_engines: cascade (default STT/LLM/TTS) and gpt_live (GPT-Live-1 voices and Responses backends). Every engine row carries has_language_axis: true for cascade, false for a voice-to-voice engine that has no separate transcriber, LLM or TTS and therefore no language set, no per-language profile overrides and no Follow the caller. Read that flag rather than matching on the engine id: agent create and the voice-stack PATCH refuse languages / language_profiles / follow_caller for an engine whose row says false. Every engine row also carries supports_navigation: false for an engine whose calls do not navigate phone menus yet (GPT-Live at this release; cascade and Gemini Live say true). Nothing is refused for such an engine: its agents keep their navigation setting, their calls run without navigation, and the voice-stack response reports `navigation.ineligible_reason` `engine_not_supported`. A TTS row uses either immutable worker-approved voices or bounded provider-directory voices already ingested by Skysay; the catalog never fetches a provider during a read. Voice metadata and tags are provider-supplied and carry provenance; Skysay does not infer age, accent, or personality. When a provider supplies no per-voice language list, the row inherits only its qualified TTS model languages. voice_count and voices_truncated say when a provider inventory is partial. A resolved agent preserves its exact selected voice in its voice-stack response even outside a bounded catalog slice. Every TTS row carries `delivery_control`: `voice_settings`, `instruction` or `speed` when a `voice_polish.delivery` profile changes that engine's audio, or `unsupported` when the engine accepts and stores a profile without changing its audio (Fish Audio and Azure neural voices today); and `delivery_profiles`, the exact value each of the four profiles resolves to on that engine (a settings object such as stability/similarity_boost/style/speed, a speaking rate, or an instruction sentence), empty for `unsupported`. Both are read from the same packaged delivery table the call runtime and the voice preview use, so a client can state what a profile changes for a voice without a provider list of its own. Each tts entry also lists default_languages: the languages in `languages` where the voice may be chosen WITHOUT the owner choosing it (for example as the pre-selected voice of a new agent). A voice can run a language without being its default -- ElevenLabs eleven_v3 runs English but lists only Estonian -- and a client that pre-selects voices should respect it. This catalog does not authorize a provider credential or disclose provider secrets.",
        "parameters": [
          {
            "in": "query",
            "name": "component",
            "required": false,
            "schema": {
              "enum": [
                "stt",
                "llm",
                "tts"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "language",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "metered_only",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Agent stack catalog"
          }
        },
        "summary": "List runnable agent stack entries"
      }
    },
    "/v1/agent-endpoints": {
      "get": {
        "description": "One bounded page, newest first, as an object: the items under `agent_endpoints` and `pagination`. Follow `pagination.next_cursor` while `pagination.has_more` is true; a walk returns every row that exists for the whole walk exactly once. `limit` is required on this list: a request without it is a 400, so a client that still reads the old array cannot mistake the refusal for an empty list and register a duplicate endpoint.",
        "parameters": [
          {
            "description": "Optional with a workspace key, which lists its own organization. An operator credential must name one: no list spans organizations.",
            "in": "query",
            "name": "organization_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only this project's rows. A project of another organization is a 400.",
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page size, 1 to 200. Required. A value outside that range or not a whole number is a 400, never shortened.",
            "in": "query",
            "name": "limit",
            "required": true,
            "schema": {
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The previous page's `pagination.next_cursor`, exactly as received. Anything else is a 400.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "agent_endpoints": {
                      "description": "At most `limit` items, at most 200.",
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/ListPagination"
                    }
                  },
                  "required": [
                    "agent_endpoints",
                    "pagination"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "One page. Server-Timing and X-Skysay-Query-Count describe the read; Cache-Control is no-store."
          },
          "400": {
            "description": "`limit` outside its range or missing where required, a `cursor` that is not a `next_cursor`, no organization for an operator credential, or a project of another organization."
          }
        },
        "summary": "List agent webhook/audio endpoints"
      },
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentEndpoint"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Agent endpoint"
          },
          "400": {
            "description": "organization_id, project_id, webhook_url or audio_stream_url is missing or blank, or events is supplied as something other than an array of known webhook event names (omit it, or send an empty array, to take the defaults)"
          }
        },
        "summary": "Connect an agent"
      }
    },
    "/v1/agent-endpoints/{agent_endpoint_id}": {
      "delete": {
        "responses": {
          "200": {
            "description": "Deleted"
          }
        },
        "summary": "Delete a webhook endpoint (soft: stops every delivery)"
      },
      "patch": {
        "parameters": [
          {
            "in": "path",
            "name": "agent_endpoint_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentEndpointEventsUpdate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Agent endpoint"
          }
        },
        "summary": "Change which events this endpoint receives"
      }
    },
    "/v1/agent-endpoints/{agent_endpoint_id}/pause": {
      "post": {
        "responses": {
          "200": {
            "description": "Agent endpoint"
          }
        },
        "summary": "Pause webhook deliveries to this endpoint"
      }
    },
    "/v1/agent-endpoints/{agent_endpoint_id}/resume": {
      "post": {
        "responses": {
          "200": {
            "description": "Agent endpoint"
          }
        },
        "summary": "Resume a paused or auto-disabled webhook endpoint"
      }
    },
    "/v1/agent-endpoints/{agent_endpoint_id}/test-event": {
      "post": {
        "responses": {
          "202": {
            "description": "Webhook delivery"
          }
        },
        "summary": "Dispatch a signed test event through the real delivery pipeline"
      }
    },
    "/v1/agent/onboarding/headless/payments": {
      "post": {
        "description": "Bound to the authenticated workspace. Available to email/social customers without a phone identity. Owner approval is required before payment. Only a signed matching Stripe webhook creates purchased funds. Browser welcome credit cannot fund numbers, PSTN or SMS. The existing URL is retained for pending-payment retries; it does not provide headless signup.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/HeadlessPayment"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HeadlessPaymentResult"
                }
              }
            },
            "description": "Payment result; pending is not wallet credit"
          },
          "400": {
            "description": "Invalid input or payment approval missing"
          },
          "401": {
            "description": "Workspace authentication required"
          },
          "402": {
            "description": "Owner approval challenge"
          },
          "409": {
            "description": "Conflicting or uncertain payment retry"
          },
          "503": {
            "description": "Payment method unavailable"
          }
        },
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "summary": "Approve a wallet top-up with hosted checkout or a shared payment token",
        "tags": [
          "Billing"
        ],
        "x-required-scopes": [
          "wallet:write"
        ]
      }
    },
    "/v1/agents": {
      "get": {
        "description": "One bounded page, newest first, as an object: the items under `agents` and `pagination`. Follow `pagination.next_cursor` while `pagination.has_more` is true; a walk returns every row that exists for the whole walk exactly once.",
        "parameters": [
          {
            "description": "Optional with a workspace key, which lists its own organization. An operator credential must name one: no list spans organizations.",
            "in": "query",
            "name": "organization_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only this project's rows. A project of another organization is a 400.",
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page size, 1 to 200; 50 when omitted. A value outside that range or not a whole number is a 400, never shortened.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The previous page's `pagination.next_cursor`, exactly as received. Anything else is a 400.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "agents": {
                      "description": "At most `limit` items, at most 200.",
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/ListPagination"
                    }
                  },
                  "required": [
                    "agents",
                    "pagination"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "One page. Server-Timing and X-Skysay-Query-Count describe the read; Cache-Control is no-store."
          },
          "400": {
            "description": "`limit` outside its range or missing where required, a `cursor` that is not a `next_cursor`, no organization for an operator credential, or a project of another organization."
          }
        },
        "summary": "List first-class agents"
      },
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Agent"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Agent. agent_endpoint.webhook_secret carries the plaintext secret that signs every event and tool call sent to this agent's webhook (X-Agent-Telco-Signature), revealed once in this response only; every later read returns [REDACTED]. Save it immediately."
          },
          "400": {
            "description": "organization_id or project_id is missing or blank; an unrecognized mode; a tools value that is neither empty nor a list; a metadata value that is neither empty nor an object; an external_audio or external_text agent missing its webhook_url/audio_stream_url; or a project_id this organization does not own. On create (unlike PATCH) a falsy tools or metadata -- null, false, 0, \"\" -- is read as an empty selection rather than refused"
          }
        },
        "summary": "Create an external or hosted agent"
      }
    },
    "/v1/agents/{agent_id}": {
      "get": {
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Agent"
          }
        },
        "summary": "Get one agent",
        "x-required-scopes": [
          "agents:read"
        ]
      },
      "patch": {
        "description": "The authenticated workspace is resolved from the target agent. For an attached Integration Action, send a connection tool attachment; the model sees only the resolved action name and input schema, never the Integration endpoint or signing secret.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentUpdate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Agent"
          },
          "400": {
            "description": "Invalid field value, a voice-stack setting sent to this route, or a status this route may not write"
          },
          "409": {
            "description": "Concurrent tool-list replacement is refused with error AgentToolsChangedError; reload the AI agent before saving. Other conflicts retain their own error code."
          }
        },
        "summary": "Update one agent",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/browser-sessions": {
      "post": {
        "description": "Workspace-authenticated only. Does not use the anonymous homepage demo budget. New sessions reserve eligible browser-only welcome credit or purchased funds and settle actual component usage; no carrier charge applies. The existing session limit is 180 seconds. Historical zero-charge snapshots remain unchanged. The selected snapshot is published or a validated draft overlay. A draft overlay naming an unknown, retired, or language-incompatible selection is rejected with a plain 400 before this point, as before. Beyond that, the resolved realtime configuration -- the agent's stack merged with any draft overlay -- is checked for runnability before anything is reserved; a config the voice worker would refuse before building media (missing prompt/first message, missing provider/model/voice, or an unsupported language) is refused here instead of reaching the worker.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "draft": {
                    "description": "Only read when snapshot is 'draft'. `first_message`, `instructions` and `language` apply to an agent without a selected language set (a published set pins the per-locale greeting, prompt and language). `stack` applies to every draft test and names its conversation engine in `stack.voice_frontend` ('cascade' or 'gpt_live') plus that engine's own fields: for cascade the `transcriber`, `model` and `voice` selections (`{provider, model, voice_id}`; leaving GPT-Live needs all three); for GPT-Live the `voice` (`{provider: 'openai', model: 'gpt-live-1', voice_id}`) and `model` (`{provider: 'openai', model: <backend>}`) -- no transcriber, a cascade one riding along is refused. A stack with no `voice_frontend` is a partial cascade overlay on the stored engine. A draft stack is refused while Follow the caller is published for the agent.",
                    "properties": {
                      "first_message": {
                        "type": "string"
                      },
                      "instructions": {
                        "type": "string"
                      },
                      "language": {
                        "type": "string"
                      },
                      "stack": {
                        "properties": {
                          "model": {
                            "type": "object"
                          },
                          "transcriber": {
                            "type": "object"
                          },
                          "voice": {
                            "type": "object"
                          },
                          "voice_frontend": {
                            "enum": [
                              "cascade",
                              "gpt_live"
                            ],
                            "type": "string"
                          }
                        },
                        "type": "object"
                      }
                    },
                    "type": "object"
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "project_id": {
                    "type": "string"
                  },
                  "snapshot": {
                    "enum": [
                      "published",
                      "draft"
                    ],
                    "type": "string"
                  },
                  "test_values": {
                    "description": "Values for the agent's declared variables, for this test call only. Built-ins (contact.*, call.*, campaign.*) are not sent. Refused like a call's values: 400 with reason one of call_variables_missing, call_variables_invalid, call_variables_over_budget, workflow_binding_failed, schema_pair_mismatch, per_call_text_on_workflow, agent_not_published, keypad_value_not_dialable, keypad_value_in_text, and field / where naming the variable or line (never a value).",
                    "type": "object"
                  }
                },
                "required": [
                  "organization_id",
                  "project_id"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Reservation and dispatch metadata for a LiveKit join token"
          },
          "400": {
            "description": "Either a plain {error, message} (an invalid draft overlay selection, unchanged from before) or, when the resolved config passes overlay validation but is still not runnable: {error: 'realtime_config_not_runnable', reason: <code>, blockers: [{code, scope, detail}]}. Codes reuse the same admission vocabulary already returned by the Agents/Numbers dashboard reads (e.g. voice_id_missing, system_prompt_missing, first_message_missing, language_not_runnable, llm_model_missing, delivery_profile_invalid). No room or reservation is created either way."
          }
        },
        "summary": "Create an authenticated LiveKit browser-test session for one hosted agent",
        "x-required-scopes": [
          "agents:write",
          "calls:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/browser-sessions/{session_id}/settle": {
      "post": {
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "session_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Settled session"
          }
        },
        "summary": "Settle one workspace browser-test reservation",
        "x-required-scopes": [
          "agents:write",
          "calls:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/chat-turns": {
      "post": {
        "description": "A text chat test of the agent as a browser voice test would run it, minus audio: the same resolved configuration (system prompt, opening line, language profile, tools, attached knowledge, and for snapshot=draft the editor's unsaved overlay), the same built-in tool prompt appends and the same language directive the voice worker applies, answered by the agent's configured Gemini model. Stateless: send the transcript so far and the new caller_input; caller_input=null with an empty transcript asks for the opening line (the configured first message verbatim, or one generated from the instructions when none is configured). Tool policy: search_knowledge_base runs the real retrieval; end_call ends the chat (ended=true, ended_reason=end_call); every other tool -- send_sms, transfer_to_human, webhook, connection and legacy MCP allowlist tools -- is HELD: the model is told the action did not happen and the call is reported in events with disposition=held. Nothing is persisted and no call record is created. Platform-funded: no wallet reservation and no component charge (only the model ran); metering reports the model calls and tokens this turn consumed. Bounded: 2000 characters per message, 40 transcript lines (the chat ends with ended_reason=transcript_limit), 24000 transcript characters, 4 tool rounds per turn and a 20-second turn budget. Refused with 400 for an agent that has a conversation flow, is not hosted, not active, GPT-Live, bound to a customer-owned model credential, or has no instructions; when no platform model credential is configured; or when the model does not answer in time. An agent WITH a conversation flow is refused with 400 here (a call with it runs the flow, so a prompt-only answer would be one no call gives) and is tested through the workflow run routes instead.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "caller_input": {
                    "maxLength": 2000,
                    "nullable": true,
                    "type": "string"
                  },
                  "draft": {
                    "description": "Honoured only with snapshot=draft: {first_message, instructions, language, stack} as the browser test sends them.",
                    "type": "object"
                  },
                  "language": {
                    "description": "A published language profile locale; defaults to the agent's language.",
                    "type": "string"
                  },
                  "snapshot": {
                    "default": "published",
                    "enum": [
                      "published",
                      "draft"
                    ],
                    "type": "string"
                  },
                  "test_values": {
                    "additionalProperties": true,
                    "description": "The values this test's call variables hold, as a call's call_context would carry them, within its bounds. The turn is admitted as a call is: the opening line and the instructions render with them, and a declared value that is required and missing, or does not match its declared type, refuses the turn with 400 and the admission code (call_variables_missing, call_variables_invalid, call_variables_over_budget). Send the same values with every turn.",
                    "type": "object"
                  },
                  "transcript": {
                    "items": {
                      "properties": {
                        "role": {
                          "enum": [
                            "caller",
                            "agent"
                          ],
                          "type": "string"
                        },
                        "text": {
                          "maxLength": 2000,
                          "type": "string"
                        }
                      },
                      "required": [
                        "role",
                        "text"
                      ],
                      "type": "object"
                    },
                    "maxItems": 40,
                    "type": "array"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "{agent_id, snapshot, language, model, reply: {role: agent, text, truncated} | null, events: [{schema, type: tool_call, seq, at_ms, name, arguments, result, ok, disposition: executed|held|ended|refused, duration_ms}], ended, ended_reason: end_call|transcript_limit|null, transcript_messages, limits, tools: {declared, omitted}, metering: {mode: platform_funded, components, model_calls, input_tokens, output_tokens}, duration_ms}"
          },
          "400": {
            "description": "Invalid or over-limit request, an agent this test cannot run, no platform model credential, or the model did not answer"
          },
          "404": {
            "description": "Agent not found (an agent in another workspace answers exactly as one that never existed)"
          }
        },
        "summary": "Send one text turn to a prompt-driven hosted agent that has no conversation flow",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/handoff": {
      "get": {
        "responses": {
          "200": {
            "description": "Agent handoff"
          }
        },
        "summary": "Get generated agent setup handoff"
      }
    },
    "/v1/agents/{agent_id}/knowledge-bases": {
      "get": {
        "responses": {
          "200": {
            "description": "Agent knowledge-base attachments"
          }
        },
        "summary": "List active knowledge bases attached to one hosted agent"
      },
      "patch": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KnowledgeBaseAttachments"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Agent knowledge-base attachments"
          }
        },
        "summary": "Replace the knowledge bases attached to one hosted agent"
      }
    },
    "/v1/agents/{agent_id}/messages": {
      "post": {
        "description": "Project-scoped, unlike the organization-scoped POST /v1/messages: the agent's own project is the send's project. A foreign workspace's agent, another project, an unpublished agent and an unknown template are the same 404. The text is rendered from the published template with the variables and slots given, within 6 SMS segments, and authorized as POST /v1/messages is.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentTemplateMessage"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "The message, as POST /v1/messages returns it. Body, provider metadata, error detail and idempotency key require sms:read, including on idempotent replays."
          },
          "400": {
            "description": "A body beside a template, an unsupported field, a missing or invalid variable or slot, or a text over 6 segments."
          },
          "404": {
            "description": "SMS template not found"
          }
        },
        "summary": "Send one of an agent's published SMS templates",
        "x-required-scopes": [
          "sms:send",
          "agents:read"
        ]
      }
    },
    "/v1/agents/{agent_id}/post-call-extraction": {
      "get": {
        "description": "Returns only the active bounded schema for this agent. A configured schema does not run inline or change a call; the separately-gated worker runs only after a completed call has a transcript.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostCallExtractionConfiguration"
                }
              }
            },
            "description": "Advisory extraction configuration"
          }
        },
        "summary": "Read the optional advisory post-call extraction configuration",
        "x-required-scopes": [
          "agents:read"
        ]
      },
      "patch": {
        "description": "The schema accepts at most 24 scalar fields. Each edit creates an immutable schema version for future calls; results are advisory and never mutate call state, policy, wallet, or customer systems.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PostCallExtractionConfiguration"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostCallExtractionConfiguration"
                }
              }
            },
            "description": "Active or disabled advisory extraction configuration"
          }
        },
        "summary": "Configure or disable bounded advisory post-call extraction",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/simulation-suite-runs/{run_id}": {
      "get": {
        "description": "Attempts page in a stable order with an explicit has_more; the query count stays constant as attempts grow. Without recordings:read, attempt transcript, recording_ref and facts are omitted, assertion actual values and rubric rationale are omitted, and linked evidence omits notes. Status, verdicts and pagination remain available at the base scope, identically for API keys and OAuth MCP callers.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "run_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 50,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Run, pinned identity, per-scenario rollup and one attempt page"
          },
          "404": {
            "description": "Run not found, or Simulations Lab is not enabled"
          }
        },
        "summary": "Fetch one run with a bounded page of attempt results",
        "x-required-scopes": [
          "agents:read"
        ]
      }
    },
    "/v1/agents/{agent_id}/simulation-suite-runs/{run_id}/cancel": {
      "post": {
        "description": "Cancellation stops unstarted work only. An attempt a worker already claimed finishes and keeps its verdict, and completed results are never altered or deleted. Cancelling an already-finished run changes nothing.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "run_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Run state after cancellation"
          },
          "404": {
            "description": "Run not found, or Simulations Lab is not enabled"
          }
        },
        "summary": "Stop the attempts of this run that have not started",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/simulation-suite-runs/{run_id}/cases/{case_result_id}/evidence": {
      "post": {
        "description": "Links, never runs. A person opened Talk in a browser or dialled the agent from a phone they had armed a test pin for; this records that it happened and which attempt it corroborates. Automated suites never originate a call, and this endpoint can only reference a session somebody else already created. The linked session must belong to the same workspace and the same agent. A phone session additionally requires that an armed phone test actually matched a real inbound call: this platform has no phone-ownership proof, so a matched pin is the nearest truthful substitute for 'verified' and no stronger claim is made. The attempt's own verdict, assertions and rubrics are unchanged -- linked evidence sits beside a verdict and never replaces one. Linking the same session twice returns the existing link with 200 rather than creating a second one.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "run_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "case_result_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "note": {
                    "maxLength": 280,
                    "type": "string"
                  },
                  "workflow_run_id": {
                    "type": "string"
                  }
                },
                "required": [
                  "workflow_run_id"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "This session was already linked to this attempt"
          },
          "201": {
            "description": "Link created"
          },
          "400": {
            "description": "The session is not a Talk or Phone session, or no armed phone test matched it"
          },
          "404": {
            "description": "Attempt or session not found, or Simulations Lab is not enabled"
          }
        },
        "summary": "Link an existing Talk or Phone session to this attempt as human evidence",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/simulation-suite-runs/{run_id}/cases/{case_result_id}/save-as-regression": {
      "post": {
        "description": "Appends a new immutable suite revision carrying the attempt's scenario as a critical regression. The historical run keeps pointing at the revision it executed, and the attempt row itself is read, never written.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "run_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "case_result_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "name": {
                    "maxLength": 120,
                    "type": "string"
                  },
                  "scenario_id": {
                    "description": "Stable id for the new scenario. Defaults to the source scenario id with a regression suffix.",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": false
        },
        "responses": {
          "201": {
            "description": "The suite's new revision and the scenario id that was added"
          },
          "404": {
            "description": "Run or case not found, or Simulations Lab is not enabled"
          },
          "409": {
            "description": "The suite changed while the regression was being saved"
          }
        },
        "summary": "Promote one attempt into a permanent scenario",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/simulation-suite-runs/{run_id}/rerun-failed": {
      "post": {
        "description": "The child reuses the parent's pinned suite revision, content hash and compiled workflow, so it asks exactly the question the original asked. The parent run and every verdict it holds are left untouched.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "run_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "idempotency_key": {
                    "maxLength": 200,
                    "type": "string"
                  }
                },
                "required": [
                  "idempotency_key"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "description": "Durable queued child run"
          },
          "400": {
            "description": "Missing idempotency_key"
          },
          "404": {
            "description": "Run not found, or Simulations Lab is not enabled"
          },
          "409": {
            "description": "The idempotency key was already used for a different request"
          },
          "422": {
            "description": "Typed refusal: no_failed_cases, unsupported_mode, active_run_limit or organization_budget"
          }
        },
        "summary": "Queue a child run over this run's failed cases",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/simulation-suite-runs/{run_id}/results": {
      "get": {
        "description": "The automation surface: built for a CI job rather than for a screen. contract_version is the promise -- fields may be ADDED under the same version, but nothing is removed or given a new meaning without it changing, so a client can ignore what it does not recognise and keep working. Attempts paginate on a stable cursor with an explicit has_more; the query count does not grow with the number of attempts. Statuses are a closed set: a run is queued, running, completed, failed, cancelled or budget_exceeded, and an attempt is pass, fail or error. errored_cases is deliberately separate from failed_cases -- an attempt whose synthetic caller or evaluator malfunctioned produced no evidence about the agent, so treating it as a failure would fail a build for a platform outage. voice_evidence_kind says which voice proof an attempt carries: offline_robustness means the authored lines were synthesized and read back, and makes no claim about barge-in, overlapping audio, playout or SIP timing; only realtime evidence can support a timing claim. unproven_assertions counts rules the attempt could not settle for want of that evidence -- they are neither passes nor failures. Transcripts, recordings and rubric rationales are not included; artifacts reports whether each is still retained, expired, or never existed.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "run_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Versioned run summary and one page of attempt verdicts"
          },
          "404": {
            "description": "Run not found, or Simulations Lab is not enabled"
          }
        },
        "summary": "Read one run's verdicts in a stable, versioned shape",
        "x-required-scopes": [
          "simulation_suites:read"
        ]
      }
    },
    "/v1/agents/{agent_id}/simulation-suites": {
      "get": {
        "description": "Bounded and stably ordered newest-first, with an opaque cursor and an explicit has_more. A suite that has never run reports state=never_run rather than a zero pass rate. Returns 404 while Simulations Lab is not enabled for the deployment.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 50,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bounded suite page with latest-run health"
          },
          "404": {
            "description": "Agent not found, or Simulations Lab is not enabled"
          }
        },
        "summary": "List this agent's simulation suites with their latest health",
        "x-required-scopes": [
          "agents:read"
        ]
      },
      "post": {
        "description": "Creates the suite and its first immutable revision. Authored scenarios are validated and bounded here; a scenario may name a runner whose runtime is not deployed yet, but a run that selects one is refused with 422 unsupported_mode.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "default_snapshot_source": {
                    "default": "draft",
                    "enum": [
                      "draft",
                      "published"
                    ],
                    "type": "string"
                  },
                  "description": {
                    "maxLength": 500,
                    "type": "string"
                  },
                  "name": {
                    "maxLength": 120,
                    "type": "string"
                  },
                  "policy": {
                    "description": "Thresholds, budget ceiling and retention for this suite.",
                    "type": "object"
                  },
                  "scenarios": {
                    "description": "Authored caller scenarios. At most 50 per suite. Each scenario's turns carry what the caller does and when: kind is speech, backchannel or silence; a silence turn carries silence_ms and no caller_input, because a silence is time rather than words; cue.after_agent_turn (1-based, and 0 meaning no cue at all: take the turn when the agent finishes) and cue.delay_ms record where inside an observed agent turn the caller speaks. Multiple cues anchored to the same turn share that observed origin, and an unobserved cue or incomplete schedule produces no scenario timing proof rather than an invented one. ground_truth_variants records alternative renderings of the same line -- a numeral written as digits or as words, say -- so a run's stored evidence carries what the caller was authored to say in every form that counts as correct. The platform's own offline recognition metric still scores against caller_input alone; the variants are carried for the readiness scorer that reads them. A silence turn needs the adaptive_voice runner and is refused with 422 silence_needs_a_room on any other, because only a room has time to spend. An adaptive_voice scenario may also carry caller_environment: a version-pinned synthetic caller MICROPHONE condition using noise_level (not the agent audio_environment volume). A scenario that supplies caller_environment must use adaptive_voice and also supply a non-blank language; it is never silently changed to English. Off/noise_level=0 is the quiet baseline; it never changes the agent's output ambience, TTS, or production STT configuration. A saved condition is authoring data, not proof that this deployment ran a realtime room.",
                    "items": {
                      "additionalProperties": true,
                      "properties": {
                        "caller_environment": {
                          "$ref": "#/components/schemas/CallerInputEnvironmentSelection"
                        }
                      },
                      "type": "object"
                    },
                    "type": "array"
                  }
                },
                "required": [
                  "name"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created suite at revision 1"
          },
          "400": {
            "description": "Invalid scenario contract or a limit exceeded"
          },
          "404": {
            "description": "Agent not found, or Simulations Lab is not enabled"
          }
        },
        "summary": "Create a simulation suite for this agent",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/simulation-suites/{suite_id}": {
      "get": {
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "suite_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Suite, scenarios, policy and run history"
          },
          "404": {
            "description": "Suite not found, or Simulations Lab is not enabled"
          }
        },
        "summary": "Fetch one suite with its authored scenarios and bounded run history",
        "x-required-scopes": [
          "agents:read"
        ]
      },
      "patch": {
        "description": "Every accepted edit appends a NEW immutable revision instead of mutating content, so a run that pinned an earlier revision keeps reading exactly what it executed. The request must carry the revision the edit is based on; a stale write returns 409 with the revision that beat it and never overwrites. Scenario personas may carry bounded interruption and intent-change rules that drive an adaptive caller, and each scenario may carry up to five semantic rubrics; a rubric is a separate evaluator call on every attempt, which is why that ceiling is low.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "suite_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "baseline_run_id": {
                    "description": "A finished run of THIS suite that later runs are compared against. Send an empty string to clear it. A run belonging to another suite, one that is still going, or one that recorded no attempts is refused with 400 -- each would make the comparison meaningless.",
                    "type": "string"
                  },
                  "default_snapshot_source": {
                    "enum": [
                      "draft",
                      "published"
                    ],
                    "type": "string"
                  },
                  "description": {
                    "maxLength": 500,
                    "type": "string"
                  },
                  "name": {
                    "maxLength": 120,
                    "type": "string"
                  },
                  "policy": {
                    "type": "object"
                  },
                  "revision": {
                    "description": "The revision this edit is based on.",
                    "type": "integer"
                  },
                  "scenarios": {
                    "description": "Complete replacement scenario list for this immutable revision. An adaptive_voice scenario may include caller_environment, a version-pinned synthetic caller MICROPHONE condition with noise_level. It is distinct from the agent's outgoing audio_environment/volume; a scenario that supplies it must use adaptive_voice and supply a non-blank language. Off requires noise_level=0 and is the quiet baseline.",
                    "items": {
                      "additionalProperties": true,
                      "properties": {
                        "caller_environment": {
                          "$ref": "#/components/schemas/CallerInputEnvironmentSelection"
                        }
                      },
                      "type": "object"
                    },
                    "type": "array"
                  },
                  "status": {
                    "enum": [
                      "active",
                      "archived"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "revision"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Suite at its new revision"
          },
          "400": {
            "description": "Missing revision or an invalid scenario contract"
          },
          "404": {
            "description": "Suite not found, or Simulations Lab is not enabled"
          },
          "409": {
            "description": "Stale revision; the response carries the revision that won"
          }
        },
        "summary": "Edit a suite under optimistic revision control",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/simulation-suites/{suite_id}/duplicate": {
      "post": {
        "description": "The copy starts its own revision chain at 1 and inherits no run history or baseline: duplicating branches authoring, it does not inherit evidence.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "suite_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "name": {
                    "maxLength": 120,
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": false
        },
        "responses": {
          "201": {
            "description": "The new suite at revision 1"
          },
          "404": {
            "description": "Suite not found, or Simulations Lab is not enabled"
          }
        },
        "summary": "Copy a suite's authored content into a new suite",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/simulation-suites/{suite_id}/runs": {
      "post": {
        "description": "Returns 202 immediately with a durable queued run: request latency never depends on how long the suite takes to execute. The suite revision, its content hash and the compiled workflow snapshot are pinned at this moment, so an edit made while the run waits for a worker cannot change what it proves. idempotency_key is required and scoped to the organization -- replaying the same request returns the same run, and reusing one key for a different request is refused with 409. A run whose selected scenarios need a runner that is not deployed is refused with 422 unsupported_mode before anything is queued. Deterministic attempts make no model call and record a cost of zero; adaptive attempts are metered, and the run pins an upper-bound estimate, a model-call ceiling and the caller and evaluator identities before the first attempt starts. Nothing here is charged to a customer balance -- suite cost is reported by the simulation-suite ledger only.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "suite_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "idempotency_key": {
                    "maxLength": 200,
                    "type": "string"
                  },
                  "pinned_source": {
                    "enum": [
                      "draft",
                      "published"
                    ],
                    "type": "string"
                  },
                  "scenario_ids": {
                    "description": "Run only these scenarios. Omit to run the whole suite.",
                    "items": {
                      "type": "string"
                    },
                    "type": "array"
                  }
                },
                "required": [
                  "idempotency_key"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "description": "Durable queued run"
          },
          "400": {
            "description": "Missing idempotency_key or an unusable snapshot"
          },
          "404": {
            "description": "Suite not found, or Simulations Lab is not enabled"
          },
          "409": {
            "description": "The idempotency key was already used for a different request"
          },
          "422": {
            "description": "Typed refusal: unsupported_mode, no_scenarios, suite_archived, active_run_limit or organization_budget"
          }
        },
        "summary": "Queue a run of this suite",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/variables": {
      "get": {
        "description": "A pure read of the stored config, with the revision a write must name and the schema_hash a call admitted now would carry. Bound to the authenticated workspace: another workspace's agent and an unknown one are the same 404.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentVariables"
                }
              }
            },
            "description": "The agent's config"
          },
          "404": {
            "description": "Agent not found"
          }
        },
        "summary": "Read an agent's call variables, SMS templates and caller lookup",
        "x-required-scopes": [
          "agents:read"
        ]
      },
      "put": {
        "description": "A full replace. The config is checked by the publish lint and republished into the agent's language profiles in the same transaction, so the next call is admitted against it; calls already admitted keep what they were admitted with. For an agent with an active workflow the config carries only sms_mode, sms_templates and caller_lookup: its variables are the workflow's. Protected fields need a workflow agent and a confirmation method; live engines (GPT-Live, Gemini Live) cannot hold them.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentVariablesUpdate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentVariables"
                }
              }
            },
            "description": "The config as stored and published"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "error": {
                      "const": "variables_config_invalid"
                    },
                    "issues": {
                      "items": {
                        "properties": {
                          "code": {
                            "description": "The publish-lint code, e.g. PROTECTED_FIELD_ON_LIVE_ENGINE, UNDECLARED, OPTIONAL_OUTSIDE_SECTION.",
                            "type": "string"
                          },
                          "field": {
                            "description": "The declared variable the issue is about.",
                            "type": "string"
                          },
                          "marker": {
                            "description": "The template marker at fault, when there is one.",
                            "type": "string"
                          },
                          "where": {
                            "description": "The part of the config or the line it was found in, e.g. variables, caller_lookup, first_message.en, sms_templates.<id>.<language>.",
                            "type": "string"
                          }
                        },
                        "required": [
                          "code"
                        ],
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "{error: 'variables_config_invalid', message, issues: [issue]}: the config fails the publish lint."
          },
          "404": {
            "description": "Agent not found"
          },
          "409": {
            "description": "{error: 'revision_conflict', message, revision}: base_revision is stale. Or {error: 'workflow_revalidation_failed', message, issues}: the change would let the agent's active workflow say a protected value before the caller is confirmed."
          }
        },
        "summary": "Replace an agent's call variables, SMS templates and caller lookup",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/voice-stack": {
      "get": {
        "description": "The target agent authorizes the tenant boundary. Returns selected/resolved STT, model, and TTS catalog metadata plus validation warnings, cost, latency, prompt, and tuning configuration. Platform-managed selections use the catalog's managed component estimate; a BYOK component selected on PATCH through an existing component credential ID has no Skysay component charge. Credential binding IDs, plaintext, ciphertext, nonce, fingerprints, and provider secrets are never returned. This GET is a pure read: it makes no provider call, validation probe, compilation, repair, lazy creation, or write.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentVoiceStack"
                }
              }
            },
            "description": "Safe voice-stack projection"
          },
          "404": {
            "description": "Agent not found"
          }
        },
        "summary": "Read a hosted agent's safe STT, model, and TTS configuration",
        "x-required-scopes": [
          "agents:read"
        ]
      },
      "patch": {
        "description": "The target agent defines the authenticated workspace. Select platform-managed components by provider/model/voice fields, or bind a component to an already-created hosted-AI credential by its ID in component_credential_bindings. The binding is a reference only: upload a BYOK secret through the separate write-only hosted-ai-credentials API; this route never accepts or returns secret material. Supplying languages and optional sparse language_profiles is an explicit publication: all effective profiles are constructible and credential-owned before an immutable version is stored for admission. `audio_environment` is a separately version-pinned outgoing-only ambience track, shared across language profiles and independent of caller STT; its optional runtime failure never replaces the selected voice/language or prevents the core call. Draft profile edits never alter existing calls or published sessions.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AgentVoiceStackUpdate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentVoiceStack"
                }
              }
            },
            "description": "Updated safe voice-stack projection"
          },
          "400": {
            "description": "Invalid component selection, credential reference, language, or tuning: {error: 'BadRequestError', message}. When a language of the stack cannot be published, the body also carries language, reason (the first code) and blockers: [{code, scope, detail}] in the admission vocabulary, e.g. reason 'voice_id_missing' when no voice is chosen for that language."
          },
          "404": {
            "description": "Agent not found"
          },
          "409": {
            "description": "Concurrent tool-list replacement is refused with error AgentToolsChangedError; reload the AI agent before saving. Other conflicts retain their own error code."
          }
        },
        "summary": "Validate and update a hosted agent's STT, model, and TTS selections",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/workflow": {
      "get": {
        "description": "The target agent authorizes the tenant boundary. This is a pure reconciliation read for PATCH/publish clients: it returns the current editable draft and revision, in-memory validation, a safe canvas-only layout, current published-version metadata, and a stable newest-first bounded version page. No provider or AI call, compilation write, repair, lazy creation, audit event, or other mutation occurs. An existing agent without a workflow draft returns 200 with workflow_state=not_created, workflow=null, published=null, and an empty versions page; only a missing or inaccessible target agent is a 404, and an agent in another workspace answers exactly as one that never existed.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "version_limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "version_offset",
            "required": false,
            "schema": {
              "default": 0,
              "maximum": 100000,
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AgentWorkflowRead"
                }
              }
            },
            "description": "Draft/not-created state and bounded version metadata"
          },
          "404": {
            "description": "Agent not found"
          }
        },
        "summary": "Read an agent's editable workflow draft and bounded published-version summary",
        "x-required-scopes": [
          "agents:read"
        ]
      },
      "patch": {
        "description": "Autosave endpoint for the canvas. Accepts a partial or full conversation_workflow.v1 draft plus optional layout (node positions/viewport/collapsed groups, never part of the compiled contract). An invalid intermediate state is allowed to save; the response's validation field reports errors/warnings without blocking the save. Send base_revision -- the revision field from the draft you edited -- to have a write refused when someone else has changed the draft since; the 409 body carries the current draft under 'current' so the caller can reconcile without another request. Omitting base_revision keeps last-write-wins.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "base_revision": {
                    "description": "The revision this edit was based on. Omit for last-write-wins.",
                    "type": "integer"
                  },
                  "layout": {
                    "type": "object"
                  },
                  "workflow": {
                    "properties": {
                      "variables": {
                        "description": "The workflow's own call variables, replacing the draft's; null removes them, and a save that leaves the key out keeps them. Checked when the workflow is published.",
                        "oneOf": [
                          {
                            "$ref": "#/components/schemas/VariablesConfig_variables"
                          },
                          {
                            "type": "null"
                          }
                        ]
                      }
                    },
                    "type": "object"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Updated workflow draft"
          },
          "404": {
            "description": "Agent has no workflow yet"
          },
          "409": {
            "description": "The draft was changed by someone else; body carries the current draft under 'current'"
          }
        },
        "summary": "Update the agent's workflow draft",
        "x-required-scopes": [
          "agents:write"
        ]
      },
      "post": {
        "description": "Creates only a workflow draft: no publish, call, tool authorization, or number attachment. From a starter template (blank, payment_reminder, payment_reminder_variables, customer_support, receptionist), blank, or a natural-language 'brief' generated by AI. template_id and brief are mutually exclusive. A brief that the model returns unreadable fails closed (400) and creates nothing; a readable-but-invalid AI-generated graph is still created so the canvas can show what needs fixing. Fails with a conflict if this agent already has a workflow -- edit the existing draft instead.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "brief": {
                    "maxLength": 4000,
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "template_id": {
                    "enum": [
                      "blank",
                      "payment_reminder",
                      "payment_reminder_variables",
                      "customer_support",
                      "receptionist"
                    ],
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": false
        },
        "responses": {
          "201": {
            "description": "Created workflow draft"
          },
          "400": {
            "description": "Both template_id and brief given, unknown template, or the AI generation provider is unavailable/unreadable"
          },
          "409": {
            "description": "Agent already has a workflow"
          }
        },
        "summary": "Create the one editable workflow draft for a hosted agent",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/workflow/ai-edit": {
      "post": {
        "description": "Given a free-text instruction, asks the AI generation provider to propose a replacement draft, then returns that proposal plus its validation -- it never writes to the draft. The caller (frontend) shows a diff and, only on explicit accept, PATCHes the existing update-draft route with the proposal, so an accepted AI edit is undoable through the same history as any manual edit. Rejects a proposal that changes more than 10 steps.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "instruction": {
                    "maxLength": 1000,
                    "type": "string"
                  }
                },
                "required": [
                  "instruction"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Proposed draft, its validation, and the changed-step count"
          },
          "400": {
            "description": "Missing instruction, the AI generation provider is unavailable/unreadable, or the proposal exceeds the bounded-edit step cap"
          },
          "404": {
            "description": "Agent has no workflow yet"
          }
        },
        "summary": "Propose a bounded AI edit to the agent's workflow draft",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/workflow/phone-pin": {
      "get": {
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{armed: false}, or the active pin's state including matched_count and last_run_id"
          }
        },
        "summary": "Read this agent's current Phone pin status",
        "x-required-scopes": [
          "agents:read"
        ]
      },
      "post": {
        "description": "The next matching inbound call to the ONE number this pin names -- from exactly this caller number, within the TTL -- is answered by a fresh run of the pinned draft or published workflow instead of the agent's normal configuration. Every other caller is unaffected, and so is every other number attached to the same agent: an agent serving both a test line and a production line never exposes an unpublished draft on the production line. from_e164 is declared by the authenticated workspace member arming it, not independently verified (this is a test aid on a phone_test_eligible number, not an access-control boundary). Arming again while a pin is still active supersedes it -- at most one active pin per agent. Fails closed if the draft workflow does not currently compile.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "from_e164": {
                    "description": "The caller number this pin matches, in any recognizable phone format.",
                    "type": "string"
                  },
                  "number_id": {
                    "description": "The workspace number this test answers on. Must be attached to this agent and phone-test eligible.",
                    "type": "string"
                  },
                  "pinned_source": {
                    "default": "draft",
                    "enum": [
                      "draft",
                      "published"
                    ],
                    "type": "string"
                  },
                  "ttl_seconds": {
                    "default": 600,
                    "maximum": 1800,
                    "minimum": 60,
                    "type": "integer"
                  }
                },
                "required": [
                  "number_id",
                  "from_e164"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Pin armed"
          },
          "400": {
            "description": "Missing from_e164 or number_id, a number that cannot answer this agent's phone test, or a draft workflow with validation errors"
          },
          "404": {
            "description": "Agent has no workflow yet, no published version yet, or the number is not in this workspace"
          }
        },
        "summary": "Arm Phone testing: pin one declared caller number to the workflow",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/workflow/phone-pin/disarm": {
      "post": {
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Whether an active pin was disarmed"
          }
        },
        "summary": "Cancel this agent's active Phone pin early",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/workflow/publish": {
      "post": {
        "description": "Blocks with 400 if the compiled draft has validation errors, or if it has warnings the caller has not acknowledged -- warnings never block by themselves, but publishing an immutable version is where the author has to have seen them. The rejection lists the outstanding codes; send them back in acknowledged_warning_codes to proceed. Version numbering is monotonic per workflow; a call always pins the exact published version it started with.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "acknowledged_warning_codes": {
                    "description": "Warning codes the author has reviewed and accepted. Codes that are not currently raised are ignored.",
                    "items": {
                      "type": "string"
                    },
                    "type": "array"
                  },
                  "base_revision": {
                    "description": "The draft revision this publish was reviewed against. The 409 body carries the current draft under 'current'. Omit to publish whatever the draft currently is.",
                    "type": "integer"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": false
        },
        "responses": {
          "201": {
            "description": "New published version"
          },
          "400": {
            "description": "Draft has validation errors, or unacknowledged warnings"
          },
          "404": {
            "description": "Agent has no workflow yet"
          },
          "409": {
            "description": "The draft was changed by someone else since base_revision; body carries the current draft under 'current'"
          }
        },
        "summary": "Publish the current draft as a new immutable workflow version",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/workflow/runs": {
      "post": {
        "description": "Pins the exact compiled draft or published version this run tests, so a later draft edit never retroactively changes a run already in progress. Only mode=chat is accepted here, because this is the only run this route can also advance: Simulations are started through POST /v1/agents/{agent_id}/workflow/simulations, Talk via POST /v1/agents/{agent_id}/browser-sessions with workflow_mode=talk, and Phone via POST /v1/agents/{agent_id}/workflow/phone-pin -- each mints its own run through the path that actually drives it. Chat runs are capped per organization to bound concurrent model calls on the platform test credential.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "mode": {
                    "enum": [
                      "chat"
                    ],
                    "type": "string"
                  },
                  "pinned_source": {
                    "default": "draft",
                    "enum": [
                      "draft",
                      "published"
                    ],
                    "type": "string"
                  },
                  "test_values": {
                    "description": "Values for the workflow's declared variables, for this Chat test only, admitted exactly as a Talk test's are: a question that compares an answer with a value compares it with these (none supplied takes its mismatch route). Refused like a call's values: 400 with reason one of call_variables_missing, call_variables_invalid, call_variables_over_budget, workflow_binding_failed, schema_pair_mismatch, per_call_text_on_workflow, agent_not_published, keypad_value_not_dialable, keypad_value_in_text, and field / where naming the variable or line (never a value).",
                    "type": "object"
                  }
                },
                "required": [
                  "mode"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created test run"
          },
          "400": {
            "description": "Invalid mode, no workflow, or the chat-run cap is full"
          },
          "404": {
            "description": "Agent has no workflow yet"
          }
        },
        "summary": "Start a Chat or Simulations test run of the agent's workflow",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/workflow/runs/{run_id}": {
      "get": {
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "run_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Run state and evidence"
          },
          "404": {
            "description": "Run not found"
          }
        },
        "summary": "Fetch one test run and its turn evidence",
        "x-required-scopes": [
          "agents:read"
        ]
      }
    },
    "/v1/agents/{agent_id}/workflow/runs/{run_id}/turns": {
      "post": {
        "description": "The decision provider is selected by the server from the deployment's configured Chat model, never by the caller. Fails closed with 400 (not 500) when no model credential is configured, or when the model's response for this turn is malformed. Only valid for mode=chat runs.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "run_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "caller_input": {
                    "maxLength": 2000,
                    "nullable": true,
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Updated run state and this turn's evidence"
          },
          "400": {
            "description": "Not a chat-mode run, or the decision provider is unavailable"
          },
          "404": {
            "description": "Run not found"
          },
          "409": {
            "description": "Run already terminal, or a concurrent turn already advanced it"
          }
        },
        "summary": "Advance one turn of a Chat test run",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/workflow/simulations": {
      "post": {
        "description": "Drives up to 20 named scenarios end-to-end synchronously (no network I/O -- scripted personas only) and reports a pass rate against each scenario's declared expected outcome. One broken scenario is reported as its own error entry rather than aborting the batch.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "pinned_source": {
                    "default": "draft",
                    "enum": [
                      "draft",
                      "published"
                    ],
                    "type": "string"
                  },
                  "scenario_ids": {
                    "items": {
                      "type": "string"
                    },
                    "maxItems": 20,
                    "type": "array"
                  }
                },
                "required": [
                  "scenario_ids"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Batch pass/fail summary and per-scenario results"
          },
          "400": {
            "description": "Empty or oversized scenario_ids list"
          }
        },
        "summary": "Run a batch of scripted-persona Simulations to completion",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/agents/{agent_id}/workflow/versions/{version}/restore": {
      "post": {
        "description": "Overwrites the current draft with a past version's snapshot. Never mutates version history; existing published versions and their call evidence are unaffected. Send base_revision -- the revision of the draft you are replacing -- to be refused when someone else has changed it since; the 409 body carries the current draft under 'current'. Omitting it overwrites whatever the draft currently is.",
        "parameters": [
          {
            "in": "path",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "version",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "base_revision": {
                    "description": "The draft revision being replaced. Omit to overwrite unconditionally.",
                    "type": "integer"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": false
        },
        "responses": {
          "200": {
            "description": "Draft restored from the named version"
          },
          "404": {
            "description": "Agent or version not found"
          },
          "409": {
            "description": "The draft was changed by someone else since base_revision; body carries the current draft under 'current'"
          }
        },
        "summary": "Restore a published version's content into the editable draft",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/api-keys": {
      "get": {
        "description": "One bounded page, newest first, as an object: the items under `api_keys` and `pagination`. Follow `pagination.next_cursor` while `pagination.has_more` is true; a walk returns every row that exists for the whole walk exactly once.",
        "parameters": [
          {
            "description": "Optional with a workspace key, which lists its own organization. An operator credential must name one: no list spans organizations.",
            "in": "query",
            "name": "organization_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page size, 1 to 200; 50 when omitted. A value outside that range or not a whole number is a 400, never shortened.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The previous page's `pagination.next_cursor`, exactly as received. Anything else is a 400.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "api_keys": {
                      "description": "At most `limit` items, at most 200.",
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/ListPagination"
                    }
                  },
                  "required": [
                    "api_keys",
                    "pagination"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "One page. Server-Timing and X-Skysay-Query-Count describe the read; Cache-Control is no-store."
          },
          "400": {
            "description": "`limit` outside its range or missing where required, a `cursor` that is not a `next_cursor`, no organization for an operator credential, or a project of another organization."
          }
        },
        "summary": "List scoped API keys"
      },
      "post": {
        "description": "Requested scopes must be workspace scopes the caller itself holds, or one of the grant bundles `all`, `all-read`, `all-write`, which are recorded by name and expanded at use time against the grant catalogue minus operator-only scopes. Operator and wildcard scopes are not grantable here; see POST /v1/operator/machine-credentials.",
        "responses": {
          "201": {
            "description": "API key with one-time token"
          },
          "403": {
            "description": "A requested scope is not grantable to a workspace key, or the caller does not hold it"
          }
        },
        "summary": "Create a scoped API key"
      }
    },
    "/v1/api-keys/scopes": {
      "get": {
        "responses": {
          "200": {
            "description": "Scope catalogue"
          }
        },
        "summary": "Read the grantable scope catalogue and bundles"
      }
    },
    "/v1/api-keys/{key_id}": {
      "get": {
        "responses": {
          "200": {
            "description": "API key"
          }
        },
        "summary": "Get one API key"
      }
    },
    "/v1/api-keys/{key_id}/revoke": {
      "post": {
        "responses": {
          "200": {
            "description": "Revoked API key"
          }
        },
        "summary": "Revoke an API key"
      }
    },
    "/v1/audio-environments": {
      "get": {
        "description": "Returns the backend-owned, versioned catalogue used by Voice Library, agent voice configuration, preview auditions and Simulation Lab. The catalogue is a pure local read: it contacts no TTS provider, makes no carrier call, changes no agent setting and returns no audio bytes. `off` is explicit. An agent's outgoing selection always pins preset id, asset version and volume; Simulation Lab's separate caller-input condition pins the same id/version with noise_level. Callers must not invent a frontend catalogue, silently substitute a retired version, or pass output ambience to STT.",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AudioEnvironmentCatalogue"
                }
              }
            },
            "description": "Versioned audio-environment catalogue"
          },
          "503": {
            "description": "Packaged immutable catalogue is unavailable or incomplete"
          }
        },
        "summary": "List immutable audio-environment presets",
        "x-required-scopes": [
          "account:read"
        ]
      }
    },
    "/v1/audit-events": {
      "get": {
        "description": "One bounded page, newest first by `created_at`, ties by creation order, as an object: the items under `audit_events` and `pagination`. Follow `pagination.next_cursor` while `pagination.has_more` is true; a walk returns every row that exists for the whole walk exactly once.",
        "parameters": [
          {
            "description": "Optional with a workspace key, which lists its own organization. An operator credential must name one: no list spans organizations.",
            "in": "query",
            "name": "organization_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page size, 1 to 500; 100 when omitted. A value outside that range or not a whole number is a 400, never shortened.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 100,
              "maximum": 500,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The previous page's `pagination.next_cursor`, exactly as received. Anything else is a 400.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "audit_events": {
                      "description": "At most `limit` items, at most 500.",
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/ListPagination"
                    }
                  },
                  "required": [
                    "audit_events",
                    "pagination"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "One page. Server-Timing and X-Skysay-Query-Count describe the read; Cache-Control is no-store."
          },
          "400": {
            "description": "`limit` outside its range or missing where required, a `cursor` that is not a `next_cursor`, no organization for an operator credential, or a project of another organization."
          }
        },
        "summary": "List audit events"
      }
    },
    "/v1/available-numbers": {
      "get": {
        "parameters": [
          {
            "description": "ISO country code, for example US. Comma-separated for several.",
            "in": "query",
            "name": "country",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Number type, for example local. Comma-separated for several.",
            "in": "query",
            "name": "number_type",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only numbers in this area code, for example 212.",
            "in": "query",
            "name": "area_code",
            "required": false,
            "schema": {
              "pattern": "^[0-9]{1,6}$",
              "type": "string"
            }
          },
          {
            "description": "Only numbers in this state or region, by name or code, for example Texas, TX or US-TX.",
            "in": "query",
            "name": "region",
            "required": false,
            "schema": {
              "maxLength": 64,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 250,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "rows (id, country, number_type, phone_number, capabilities, customer prices, area_code, area_name, city, region, region_code, checked_at, expires_at), inventory_mode (exact_cached|category_fallback) and has_more"
          },
          "400": {
            "description": "area_code is not 1-6 digits, region is longer than 64 characters, or limit is not an integer"
          }
        },
        "summary": "List the exact numbers currently loaded for purchase. Each row is an availability snapshot, not a reservation: availability and price are rechecked with the carrier before the number is ordered, so a row past its expires_at can still be chosen"
      }
    },
    "/v1/available-numbers/search": {
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "area_code": {
                    "description": "Only numbers in this area code, for example 212",
                    "pattern": "^[0-9]{1,6}$",
                    "type": "string"
                  },
                  "country": {
                    "description": "ISO country code, for example US",
                    "type": "string"
                  },
                  "limit": {
                    "default": 10,
                    "maximum": 20,
                    "minimum": 1,
                    "type": "integer"
                  },
                  "number_type": {
                    "default": "local",
                    "type": "string"
                  },
                  "region": {
                    "description": "Only numbers in this state or region, by name or code, for example Texas, TX or US-TX",
                    "maxLength": 64,
                    "type": "string"
                  }
                },
                "required": [
                  "country"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The rows as GET /v1/available-numbers returns them, the area_code and region searched, refresh_status (succeeded|fresh_cache|cooldown|failed|blocked) and expires_at. Only succeeded and fresh_cache mean the rows are current carrier inventory; cooldown and failed return no rows"
          },
          "400": {
            "description": "country is missing, area_code is not 1-6 digits, region is longer than 64 characters, or limit is not an integer"
          }
        },
        "summary": "Load exact numbers from the carrier for one country and number type, optionally narrowed by area code or region. A safe read: nothing is reserved, ordered or charged. A recent identical search is answered from the loaded snapshot or its cooldown instead of reading the carrier again"
      }
    },
    "/v1/billing/invoices": {
      "get": {
        "description": "One bounded page, newest first by `created_at`, ties by creation order, as an object: the items under `invoices` and `pagination`. Follow `pagination.next_cursor` while `pagination.has_more` is true; a walk returns every row that exists for the whole walk exactly once. Top-up ledger entries only.",
        "parameters": [
          {
            "description": "Optional with a workspace key, which lists its own organization. An operator credential must name one: no list spans organizations.",
            "in": "query",
            "name": "organization_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page size, 1 to 500; 100 when omitted. A value outside that range or not a whole number is a 400, never shortened.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 100,
              "maximum": 500,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The previous page's `pagination.next_cursor`, exactly as received. Anything else is a 400.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "count": {
                      "description": "Invoices on this page.",
                      "minimum": 0,
                      "type": "integer"
                    },
                    "currency": {
                      "type": "string"
                    },
                    "invoices": {
                      "description": "At most `limit` items, at most 500.",
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "organization_id": {
                      "type": "string"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/ListPagination"
                    }
                  },
                  "required": [
                    "invoices",
                    "pagination",
                    "organization_id",
                    "currency",
                    "count"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "One page. Server-Timing and X-Skysay-Query-Count describe the read; Cache-Control is no-store."
          },
          "400": {
            "description": "`limit` outside its range or missing where required, a `cursor` that is not a `next_cursor`, no organization for an operator credential, or a project of another organization."
          }
        },
        "summary": "List wallet top-ups with their receipt and invoice links"
      }
    },
    "/v1/billing/settings": {
      "get": {
        "responses": {
          "200": {
            "description": "Billing settings"
          }
        },
        "summary": "Read wallet billing settings and auto-recharge readiness"
      },
      "patch": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BillingSettings"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Billing settings"
          }
        },
        "summary": "Update safe auto-recharge settings without triggering a live charge"
      }
    },
    "/v1/billing/stripe/topup-sessions": {
      "post": {
        "description": "Optional number_request_id must belong to the authenticated workspace/project and still be payment_required (requires numbers:read in addition to wallet:write). Return selection is rebuilt from this validated request and the configured website origin. Only a signed Stripe webhook credits funds. A still-payable or uncertain checkout with a different return context is refused; no second checkout is created.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StripeTopup"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Stripe checkout session or disabled status"
          },
          "404": {
            "description": "Request not found in workspace project"
          },
          "409": {
            "description": "Stale request or conflicting open checkout (reason topup_context_conflict, known checkout_url when available)"
          }
        },
        "summary": "Create a Stripe checkout topup session when enabled"
      }
    },
    "/v1/billing/stripe/webhook": {
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Stripe webhook processing result"
          }
        },
        "summary": "Receive a signed Stripe checkout webhook and credit the wallet idempotently"
      }
    },
    "/v1/byo/sip-trunk/dids": {
      "post": {
        "responses": {
          "200": {
            "description": "Bounded DID list with channels, voice-only capability, and already-imported flags"
          }
        },
        "summary": "List owned DIDLogic or DIDWW numbers using a transient customer API key; discovery never changes routing"
      }
    },
    "/v1/byo/sip-trunk/import": {
      "post": {
        "responses": {
          "201": {
            "description": "Number with separate incoming/outgoing configuration results, or local disconnect result"
          },
          "400": {
            "description": "organization_id or project_id is missing or blank, a project_id this organization does not own, or a missing/unparseable e164"
          }
        },
        "summary": "Connect a selected customer-owned voice number after confirmation, or disconnect its local runtime access; carrier bills rental and traffic"
      }
    },
    "/v1/byo/sip-trunk/setup-checks": {
      "post": {
        "responses": {
          "200": {
            "description": "Readiness steps plus the provider activation-evidence block"
          }
        },
        "summary": "Preview one DIDWW routing change and issue a scoped confirmation token, or check existing DIDLogic SIP readiness"
      }
    },
    "/v1/calendars/oauth/calcom/complete": {
      "post": {
        "description": "First-party signed-in website-session command. API keys cannot start or complete consent (403). One-use, provider/project/session-bound state. No application or provider tokens returned.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "code": {
                    "type": "string",
                    "writeOnly": true
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "project_id": {
                    "type": "string"
                  },
                  "state": {
                    "type": "string"
                  }
                },
                "required": [
                  "state",
                  "code"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Authorization URL or safe connection metadata"
          },
          "403": {
            "description": "Missing, mismatched, expired or replayed consent session"
          }
        },
        "summary": "calcom calendar OAuth complete"
      }
    },
    "/v1/calendars/oauth/calcom/start": {
      "post": {
        "description": "First-party signed-in website-session command. API keys cannot start or complete consent (403). One-use, provider/project/session-bound state. No application or provider tokens returned.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "code": {
                    "type": "string",
                    "writeOnly": true
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "project_id": {
                    "type": "string"
                  },
                  "state": {
                    "type": "string"
                  }
                },
                "required": [
                  "project_id"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Authorization URL or safe connection metadata"
          },
          "403": {
            "description": "Missing, mismatched, expired or replayed consent session"
          }
        },
        "summary": "calcom calendar OAuth start"
      }
    },
    "/v1/calendars/oauth/calendly/complete": {
      "post": {
        "description": "First-party signed-in website-session command. API keys cannot start or complete consent (403). One-use, provider/project/session-bound state. No application or provider tokens returned.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "code": {
                    "type": "string",
                    "writeOnly": true
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "project_id": {
                    "type": "string"
                  },
                  "state": {
                    "type": "string"
                  }
                },
                "required": [
                  "state",
                  "code"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Authorization URL or safe connection metadata"
          },
          "403": {
            "description": "Missing, mismatched, expired or replayed consent session"
          }
        },
        "summary": "calendly calendar OAuth complete"
      }
    },
    "/v1/calendars/oauth/calendly/start": {
      "post": {
        "description": "First-party signed-in website-session command. API keys cannot start or complete consent (403). One-use, provider/project/session-bound state. No application or provider tokens returned.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "code": {
                    "type": "string",
                    "writeOnly": true
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "project_id": {
                    "type": "string"
                  },
                  "state": {
                    "type": "string"
                  }
                },
                "required": [
                  "project_id"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Authorization URL or safe connection metadata"
          },
          "403": {
            "description": "Missing, mismatched, expired or replayed consent session"
          }
        },
        "summary": "calendly calendar OAuth start"
      }
    },
    "/v1/calendars/oauth/google/complete": {
      "post": {
        "description": "First-party signed-in website-session command. API keys cannot start or complete consent (403). One-use, provider/project/session-bound state. No application or provider tokens returned.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "code": {
                    "type": "string",
                    "writeOnly": true
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "project_id": {
                    "type": "string"
                  },
                  "state": {
                    "type": "string"
                  }
                },
                "required": [
                  "state",
                  "code"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Authorization URL or safe connection metadata"
          },
          "403": {
            "description": "Missing, mismatched, expired or replayed consent session"
          }
        },
        "summary": "google calendar OAuth complete"
      }
    },
    "/v1/calendars/oauth/google/start": {
      "post": {
        "description": "First-party signed-in website-session command. API keys cannot start or complete consent (403). One-use, provider/project/session-bound state. No application or provider tokens returned.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "code": {
                    "type": "string",
                    "writeOnly": true
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "project_id": {
                    "type": "string"
                  },
                  "state": {
                    "type": "string"
                  }
                },
                "required": [
                  "project_id"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Authorization URL or safe connection metadata"
          },
          "403": {
            "description": "Missing, mismatched, expired or replayed consent session"
          }
        },
        "summary": "google calendar OAuth start"
      }
    },
    "/v1/calendars/oauth/microsoft/complete": {
      "post": {
        "description": "First-party signed-in website-session command. API keys cannot start or complete consent (403). One-use, provider/project/session-bound state. No application or provider tokens returned.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "code": {
                    "type": "string",
                    "writeOnly": true
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "project_id": {
                    "type": "string"
                  },
                  "state": {
                    "type": "string"
                  }
                },
                "required": [
                  "state",
                  "code"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Authorization URL or safe connection metadata"
          },
          "403": {
            "description": "Missing, mismatched, expired or replayed consent session"
          }
        },
        "summary": "microsoft calendar OAuth complete"
      }
    },
    "/v1/calendars/oauth/microsoft/start": {
      "post": {
        "description": "First-party signed-in website-session command. API keys cannot start or complete consent (403). One-use, provider/project/session-bound state. No application or provider tokens returned.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "code": {
                    "type": "string",
                    "writeOnly": true
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "project_id": {
                    "type": "string"
                  },
                  "state": {
                    "type": "string"
                  }
                },
                "required": [
                  "project_id"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Authorization URL or safe connection metadata"
          },
          "403": {
            "description": "Missing, mismatched, expired or replayed consent session"
          }
        },
        "summary": "microsoft calendar OAuth start"
      }
    },
    "/v1/calendars/{connection_id}/appointments/{appointment_id}/resolve": {
      "post": {
        "description": "agents:write; bound to the authenticated organization/project and connection. Only unconfirmed requests can change; submitting requests and active reconciliation reads are refused. Inspect the provider account first. No provider request or booking retry is performed; the resolution is audited. Retrying the identical resolution is idempotent.",
        "parameters": [
          {
            "in": "path",
            "name": "connection_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "appointment_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "organization_id": {
                    "type": "string"
                  },
                  "project_id": {
                    "type": "string"
                  },
                  "provider_checked": {
                    "const": true,
                    "type": "boolean"
                  },
                  "provider_id": {
                    "description": "Required for booked; omit for verified not-created.",
                    "maxLength": 1024,
                    "type": "string"
                  },
                  "state": {
                    "enum": [
                      "booked",
                      "not_created"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "project_id",
                  "provider_checked",
                  "state"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Safe appointment state and booking_succeeded flag"
          },
          "400": {
            "description": "Account verification or booking identity missing"
          },
          "404": {
            "description": "Appointment or connection not in workspace/project"
          },
          "409": {
            "description": "Appointment is submitting, verification is in progress, or the appointment was already resolved differently"
          }
        },
        "summary": "Record account-verified outcome of an uncertain appointment"
      }
    },
    "/v1/calendars/{connection_id}/configure": {
      "post": {
        "description": "Bound to the authenticated organization and a project in that organization. agents:write required. Tokens and provider errors are never returned. selections is an explicit network command, bounded to 100 results with has_more.",
        "parameters": [
          {
            "in": "path",
            "name": "connection_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "configuration": {
                    "$ref": "#/components/schemas/CalendarConfiguration"
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "project_id": {
                    "type": "string"
                  }
                },
                "required": [
                  "project_id",
                  "configuration"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Safe connection metadata or bounded selection list"
          },
          "400": {
            "description": "Invalid policy or unsupported provider capability"
          },
          "404": {
            "description": "Connection not found in this workspace/project"
          }
        },
        "summary": "Choose calendar destination and booking policy"
      }
    },
    "/v1/calendars/{connection_id}/disable": {
      "post": {
        "description": "Bound to the authenticated organization and a project in that organization. agents:write required. Tokens and provider errors are never returned. selections is an explicit network command, bounded to 100 results with has_more.",
        "parameters": [
          {
            "in": "path",
            "name": "connection_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "organization_id": {
                    "type": "string"
                  },
                  "project_id": {
                    "type": "string"
                  }
                },
                "required": [
                  "project_id"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Safe connection metadata or bounded selection list"
          },
          "400": {
            "description": "Invalid policy or unsupported provider capability"
          },
          "404": {
            "description": "Connection not found in this workspace/project"
          }
        },
        "summary": "Disconnect a calendar and erase locally stored tokens"
      }
    },
    "/v1/calendars/{connection_id}/selections": {
      "post": {
        "description": "Bound to the authenticated organization and a project in that organization. agents:write required. Tokens and provider errors are never returned. selections is an explicit network command, bounded to 100 results with has_more.",
        "parameters": [
          {
            "in": "path",
            "name": "connection_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "organization_id": {
                    "type": "string"
                  },
                  "project_id": {
                    "type": "string"
                  }
                },
                "required": [
                  "project_id"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Safe connection metadata or bounded selection list"
          },
          "400": {
            "description": "Invalid policy or unsupported provider capability"
          },
          "404": {
            "description": "Connection not found in this workspace/project"
          }
        },
        "summary": "Fetch account calendar or event-type choices"
      }
    },
    "/v1/call-artifact-imports": {
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CallArtifactImport"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Imported artifacts"
          }
        },
        "summary": "Import local call recordings and artifacts for demos"
      }
    },
    "/v1/call-results": {
      "get": {
        "description": "Reads a customer-owned external_group_reference inside the authenticated organization/project. This is a pure, provider-free reconciliation read: it does not create calls, retry recipients, classify a business outcome, or copy recordings/transcripts. It returns authoritative technical call facts and a versioned, allowlisted technical_failure envelope separately from optional advisory extraction, plus authenticated evidence anchors (turn identifiers and offsets only). Historical failures without versioned evidence remain unknown and are never inferred from duration. The answered_or_voicemail metric deliberately includes only observable SIP/media answer evidence and may include voicemail; human_answered remains not_provable until a separately validated signal exists. Treat pagination as mandatory: a page is not the whole group while pagination.has_more is true.",
        "parameters": [
          {
            "description": "The authenticated organization; must match the bearer workspace.",
            "in": "query",
            "name": "organization_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Project inside the authenticated organization.",
            "in": "query",
            "name": "project_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Opaque customer-owned group key. Skysay does not assign campaign, debt, or retry semantics to it.",
            "in": "query",
            "name": "external_group_reference",
            "required": true,
            "schema": {
              "maxLength": 256,
              "minLength": 1,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "terminal_status",
            "required": false,
            "schema": {
              "default": "all",
              "enum": [
                "all",
                "terminal",
                "non_terminal"
              ],
              "type": "string"
            }
          },
          {
            "description": "ready requires both an available recording and at least one transcript turn.",
            "in": "query",
            "name": "artifact_readiness",
            "required": false,
            "schema": {
              "default": "all",
              "enum": [
                "all",
                "ready",
                "not_ready"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "extraction_state",
            "required": false,
            "schema": {
              "default": "all",
              "enum": [
                "all",
                "not_configured",
                "pending",
                "ready",
                "failed"
              ],
              "type": "string"
            }
          },
          {
            "description": "Filters only generic platform-quality facts, never customer business classifications.",
            "in": "query",
            "name": "platform_exception",
            "required": false,
            "schema": {
              "default": "all",
              "enum": [
                "all",
                "only",
                "none"
              ],
              "type": "string"
            }
          },
          {
            "description": "Opaque page_cursor from the previous response; continue with the returned next_cursor.",
            "in": "query",
            "name": "before",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallGroupResults"
                }
              }
            },
            "description": "Bounded factual call result page. Server-Timing and X-Skysay-Query-Count describe the fixed read budget; Cache-Control is no-store."
          },
          "400": {
            "description": "Missing/malformed group key, filter, limit, or page cursor."
          },
          "404": {
            "description": "Project not found in the authenticated organization."
          }
        },
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "summary": "Read one bounded factual post-call result group",
        "x-required-scopes": [
          "calls:read",
          "recordings:read"
        ]
      }
    },
    "/v1/calls": {
      "get": {
        "responses": {
          "200": {
            "description": "Calls"
          }
        },
        "summary": "List calls"
      },
      "post": {
        "description": "Select the saved hosted agent with agent_id; the call begins from that agent's stored STT, model, and TTS stack. agent_endpoint_id is a backwards-compatible alias, not a second stack selector. Supply Idempotency-Key (preferred) or idempotency_key. Replays with the same canonical request return the original call; reusing the key for a different request returns 409. call_context is a small, non-secret JSON object made available to the managed worker at call start, never a substitute for live customer data or a way to change the selected voice stack.",
        "parameters": [
          {
            "description": "Durably deduplicates application retry requests within the organization.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "maxLength": 200,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OutboundCall"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Call"
          },
          "400": {
            "description": "Either a plain {error, message} (a malformed request), or, when the request is well formed but the selected agent's published configuration is one the voice worker could not build a call from: {error: 'realtime_config_not_runnable', reason: <code>, blockers: [{code, scope, detail}]}. Codes reuse the same admission vocabulary the Agents dashboard read returns (e.g. system_prompt_missing, voice_id_missing, first_message_missing, tts_not_runnable, language_not_runnable, delivery_profile_invalid). No call, wallet reservation or origination job is created; fix the agent's voice setup and retry."
          },
          "409": {
            "description": "Idempotency key was reused for a different request, or the call can no longer be canceled."
          },
          "429": {
            "description": "The bounded outbound queue is full. Includes Retry-After and retry_after_seconds."
          }
        },
        "summary": "Place one policy-gated, idempotent outbound call"
      }
    },
    "/v1/calls/inbound/route": {
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InboundCallRoute"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Inbound call route result"
          }
        },
        "summary": "Route an inbound dedicated-number call to its assigned agent"
      }
    },
    "/v1/calls/stats": {
      "get": {
        "description": "Aggregation runs in SQL over the whole call history for the organization/project, not just a bounded list page, so the figures stay exact however long that history grows. Pass organization_id (and optionally project_id) as query parameters the same way GET /v1/calls does. window is repeated, each in the form <key>:<since-timestamp> (an RFC3339 UTC timestamp); at least one is required and at most 8 may be requested in one call. until is an optional exclusive upper bound (RFC3339 UTC, defaults to now) shared by every window. Pass agent_window naming one of the requested window keys to additionally receive a per-agent breakdown over that one window. That breakdown is a bounded page ordered by agent_id: check agents_has_more and continue with agent_after=<agents_next_cursor> rather than reading one page as the organization's whole agent list. The window totals themselves are never truncated -- they are aggregated in SQL over the entire history in scope.",
        "parameters": [
          {
            "description": "The authenticated organization; must match the bearer's own workspace.",
            "in": "query",
            "name": "organization_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Optional project in the authenticated organization. Omit to aggregate the whole organization.",
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Exclusive upper bound shared by every window. Defaults to the current time.",
            "in": "query",
            "name": "until",
            "required": false,
            "schema": {
              "format": "date-time",
              "type": "string"
            }
          },
          {
            "description": "Repeated '<key>:<since>' pairs, e.g. window=today:2026-08-27T00:00:00Z. key is 1-32 characters of [A-Za-z0-9_-] and must not repeat across the request. At least one is required.",
            "explode": true,
            "in": "query",
            "name": "window",
            "required": true,
            "schema": {
              "items": {
                "type": "string"
              },
              "maxItems": 8,
              "minItems": 1,
              "type": "array"
            },
            "style": "form"
          },
          {
            "description": "One of the window keys already requested. When set, the response also includes a per-agent breakdown over that window.",
            "in": "query",
            "name": "agent_window",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Maximum agents in the agent_window breakdown. Validated whenever it is sent -- a non-numeric value is a 400 either way -- but it only changes the response when agent_window is set.",
            "in": "query",
            "name": "agent_limit",
            "required": false,
            "schema": {
              "default": 100,
              "maximum": 500,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Continue the agent breakdown after this agent_id -- pass agents_next_cursor from the previous response. Only changes the response when agent_window is set.",
            "in": "query",
            "name": "agent_after",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallStats"
                }
              }
            },
            "description": "Call stats"
          },
          "400": {
            "description": "Missing/duplicate/malformed window, a malformed until, unknown agent_window, or more than 8 windows requested."
          }
        },
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "summary": "Aggregate call volume, talk time, and spend over one or more date windows",
        "x-required-scopes": [
          "calls:read"
        ]
      }
    },
    "/v1/calls/{call_id}": {
      "get": {
        "description": "The same projection GET /v1/calls returns, for a single call. A live browser test (direction=web_test) is a real call and is returned here; a call your agent placed inside a simulation-suite run is platform-synthetic and is not found here.",
        "parameters": [
          {
            "in": "path",
            "name": "call_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallDetail"
                }
              }
            },
            "description": "Call, with where its workflow ended"
          },
          "404": {
            "description": "Call not found"
          }
        },
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "summary": "Get one call",
        "x-required-scopes": [
          "calls:read"
        ]
      }
    },
    "/v1/calls/{call_id}/artifacts": {
      "get": {
        "responses": {
          "200": {
            "description": "Artifacts"
          }
        },
        "summary": "List call artifacts"
      }
    },
    "/v1/calls/{call_id}/cancel": {
      "post": {
        "description": "A queued call with an unclaimed originate job, or a call held for review (status needs_review), can be canceled. A queued call's wallet reservation is released; a held call never reserved anything. Once a worker has claimed the job, this endpoint fails closed rather than implying the carrier call was stopped.",
        "responses": {
          "200": {
            "description": "Canceled call"
          },
          "409": {
            "description": "Call is already being originated, terminal, or has no queued originate job."
          }
        },
        "summary": "Cancel an outbound call before origination"
      }
    },
    "/v1/calls/{call_id}/events": {
      "get": {
        "description": "One bounded page of this call's call_events rows, oldest first, each with its raw event payload. This is the platform's own internal bookkeeping log, not a webhook replay: it includes types never delivered to any webhook endpoint at all (e.g. wallet.reserved, call.authorization, call.policy_evaluated, number.matched), and every row here is this row's own id/type/payload, never the signed webhook envelope. call.failed is written here too (payload shape varies by writer -- see CallEvent.payload), always thinner than the richer result body its webhook delivers separately. call.completed is the one terminal type NEVER written here at all -- it is derived from the call's final status and delivered only to webhooks; look at status on GET /v1/calls/{call_id} for that outcome instead. The response body is the plain array it has always been; the paging state is in headers. Do NOT read one page as the whole history: check X-Has-More, and continue with after=<the last row's page_cursor> until it is false. A long-running call with many agent tool invocations (each is its own call.agent_tool_call row) is exactly the case that needs more than one page.",
        "parameters": [
          {
            "in": "path",
            "name": "call_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Maximum events to return in this page.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 200,
              "maximum": 1000,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Continue after a row you already hold: pass its page_cursor. Treat the value as opaque. Unlike an offset it does not move when new events are written mid-walk.",
            "in": "query",
            "name": "after",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/CallEvent"
                  },
                  "type": "array"
                }
              }
            },
            "description": "One page of call events, oldest first",
            "headers": {
              "X-Has-More": {
                "description": "`true` when older-than-this-page events remain. A page is NOT the whole history unless this is `false`.",
                "schema": {
                  "enum": [
                    "true",
                    "false"
                  ],
                  "type": "string"
                }
              },
              "X-Limit": {
                "description": "The page size actually applied, after clamping.",
                "schema": {
                  "type": "integer"
                }
              },
              "X-Next-Cursor": {
                "description": "Present only when X-Has-More is true: the page_cursor to pass as `after` for the next page.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Returned-Count": {
                "description": "Events in this page.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "400": {
            "description": "limit is not a number or is out of range, or after is not a page cursor."
          },
          "404": {
            "description": "Call not found"
          }
        },
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "summary": "Get one call's full internal lifecycle event log",
        "x-required-scopes": [
          "calls:read"
        ]
      }
    },
    "/v1/calls/{call_id}/hangup": {
      "post": {
        "description": "Requests teardown of a call that is already live. This is also what the Hang up button on a live call in the workspace Calls page sends. Returns 202 with `hangup_requested_at`; it never reports the call as ended, because the media session is torn down by the worker holding it and the call reaches `call.ended` through the ordinary terminal path. The call then reports `ended_reason` `hangup_requested`, unless it had already started ending another way, in which case that ending's reason is reported. Repeating the request is idempotent and returns the original timestamp. Until a worker acknowledges, the request stays visible with `status` `pending`. If the worker reports that the teardown FAILED, the call is still up, so a further request re-arms it with a fresh timestamp and the previous failure's reason stays visible beside it; after three consecutive failed teardowns further requests are refused so the call is escalated rather than retried. A call still queued for origination is refused: cancel it with POST /v1/calls/{call_id}/cancel instead. For a workspace caller, a call in another workspace, or a Simulations Lab run, answers exactly as a call id that does not exist. The response carries `hangup_requested_at`, `status` (`pending` or `acknowledged`), `hangup_acknowledged_at`, `hangup_acknowledged_outcome`, `hangup_acknowledged_reason`, `hangup_failed_attempts`, `hangup_unacknowledged_seconds` (null once acknowledged), `hangup_attempt` (which attempt this is; a re-arm increments it) and `already_requested`, which is false for a first or re-armed request and true when an existing one was returned unchanged.",
        "responses": {
          "202": {
            "description": "Hangup requested; not yet acknowledged by the media worker."
          },
          "404": {
            "description": "No such call in this workspace: unknown, another workspace's, or a Simulations Lab run."
          },
          "409": {
            "description": "Call is not live (already terminal, or still queued: use cancel), or its teardown has already failed three times."
          }
        },
        "summary": "Ask a live call to hang up",
        "x-required-scopes": [
          "calls:write"
        ]
      }
    },
    "/v1/calls/{call_id}/observability": {
      "get": {
        "description": "Bound to the authenticated workspace call before owner checks. Includes a safe appointment projection (id, UTC interval, timezone and state: submitting, booked, unavailable, unconfirmed or not_created); credentials, provider identities and reconciliation leases are excluded. closing_evidence contains bounded post-call close-owner receipts and an independent canonical ending. A tool result, accepted request, owner commit or provider close command is not proof of disconnection. Missing historical receipts stay unavailable. The existing paginated transcript API remains a stream of turns and uncaptioned caller moments; closing/tool receipts are obtained here. No live receipt transport is provided.",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "closing_evidence": {
                      "$ref": "#/components/schemas/CallClosingEvidence"
                    }
                  },
                  "type": "object"
                }
              }
            },
            "description": "Call observability bundle"
          }
        },
        "summary": "Get dashboard call observability bundle"
      }
    },
    "/v1/calls/{call_id}/post-call-extraction": {
      "get": {
        "description": "Returns not_configured when this call had no configured extraction schema. Results are asynchronously derived from a bounded transcript after completion and are never authoritative for billing, policy, or customer-system state.",
        "parameters": [
          {
            "in": "path",
            "name": "call_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostCallExtractionResult"
                }
              }
            },
            "description": "Advisory extraction result or not_configured"
          }
        },
        "summary": "Read an optional advisory extraction result for one call",
        "x-required-scopes": [
          "recordings:read"
        ]
      }
    },
    "/v1/calls/{call_id}/recording": {
      "delete": {
        "responses": {
          "200": {
            "description": "Deleted recording"
          }
        },
        "summary": "Delete the active call recording metadata and audit the action"
      },
      "get": {
        "description": "The returned download_url is the stable authenticated playback contract. Clients must not construct storage URLs from storage_key or media_url.",
        "parameters": [
          {
            "in": "path",
            "name": "call_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallRecording"
                }
              }
            },
            "description": "Recording metadata"
          },
          "404": {
            "description": "The call is not in the authenticated workspace, or it has no recording -- a call in another workspace answers exactly as one that never existed"
          }
        },
        "summary": "Get call recording metadata and disclosure state",
        "x-required-scopes": [
          "recordings:read"
        ]
      }
    },
    "/v1/calls/{call_id}/recording/content": {
      "get": {
        "description": "Returns tenant-authorized recording bytes. A single Range: bytes=... request is supported for browser playback; multipart ranges are rejected with 416.",
        "parameters": [
          {
            "in": "path",
            "name": "call_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "One byte range, for example bytes=0- or bytes=-4096.",
            "in": "header",
            "name": "Range",
            "required": false,
            "schema": {
              "pattern": "^[Bb][Yy][Tt][Ee][Ss]=(?:[0-9]+-[0-9]*|-[0-9]+)$",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/octet-stream": {
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              },
              "audio/mpeg": {
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              },
              "audio/ogg": {
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              },
              "audio/wav": {
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              },
              "audio/webm": {
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              }
            },
            "description": "Complete recording",
            "headers": {
              "Accept-Ranges": {
                "example": "bytes",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "example": "private, no-store",
                "schema": {
                  "type": "string"
                }
              },
              "Content-Length": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-Content-Type-Options": {
                "example": "nosniff",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "206": {
            "content": {
              "application/octet-stream": {
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              },
              "audio/mpeg": {
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              },
              "audio/ogg": {
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              },
              "audio/wav": {
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              },
              "audio/webm": {
                "schema": {
                  "format": "binary",
                  "type": "string"
                }
              }
            },
            "description": "Requested byte range",
            "headers": {
              "Accept-Ranges": {
                "example": "bytes",
                "schema": {
                  "type": "string"
                }
              },
              "Cache-Control": {
                "example": "private, no-store",
                "schema": {
                  "type": "string"
                }
              },
              "Content-Length": {
                "schema": {
                  "type": "integer"
                }
              },
              "Content-Range": {
                "example": "bytes 0-1023/4096",
                "schema": {
                  "type": "string"
                }
              },
              "X-Content-Type-Options": {
                "example": "nosniff",
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer credential"
          },
          "403": {
            "description": "The credential lacks recordings:read"
          },
          "404": {
            "description": "The call is not in the authenticated workspace, the recording content is unavailable, or it is no longer registered -- one indistinguishable answer, so the status cannot be used to discover which call ids exist"
          },
          "416": {
            "description": "Invalid, multipart, or unsatisfiable byte range",
            "headers": {
              "Content-Length": {
                "example": 0,
                "schema": {
                  "type": "integer"
                }
              },
              "Content-Range": {
                "description": "Unsatisfied range form: bytes */<complete-length>.",
                "example": "bytes */4096",
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        },
        "summary": "Stream call recording audio",
        "x-required-scopes": [
          "recordings:read"
        ]
      }
    },
    "/v1/calls/{call_id}/transcript": {
      "get": {
        "description": "Read one bounded original/translation timeline page with canonical IDs, source_revision, unchanged recording anchors, missing-caption events and pagination {limit, returned, has_more, next_cursor}. Follow next_cursor while has_more. Oversized source units return explicit unavailable placeholders so later events remain reachable. Cursors bind the source revision; refresh after a changed source. A selected translation language only reads a cached artifact and never creates work.",
        "parameters": [
          {
            "in": "path",
            "name": "call_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "translation_language",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 200,
              "maximum": 500,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallTranscriptPage"
                }
              }
            },
            "description": "One bounded, revision-consistent transcript page."
          },
          "409": {
            "description": "Source revision changed; restart pagination from the first page."
          }
        },
        "summary": "Get call transcript turns",
        "x-required-scopes": [
          "recordings:read"
        ]
      }
    },
    "/v1/calls/{call_id}/transcript/stream": {
      "get": {
        "description": "Read one bounded original/translation timeline page with canonical IDs, source_revision, unchanged recording anchors, missing-caption events and pagination {limit, returned, has_more, next_cursor}. Follow next_cursor while has_more. Oversized source units return explicit unavailable placeholders so later events remain reachable. Cursors bind the source revision; refresh after a changed source. A selected translation language only reads a cached artifact and never creates work.",
        "parameters": [
          {
            "in": "path",
            "name": "call_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "translation_language",
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 200,
              "maximum": 500,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallTranscriptStreamPage"
                }
              }
            },
            "description": "One bounded, revision-consistent transcript page."
          },
          "409": {
            "description": "Source revision changed; restart pagination from the first page."
          }
        },
        "summary": "Get transcript stream events",
        "x-required-scopes": [
          "recordings:read"
        ]
      }
    },
    "/v1/calls/{call_id}/transcript/translations": {
      "get": {
        "description": "Pure read: target selection, original/translation/both display, and polling never invoke a provider or create paid work. Artifacts are fenced to the current source revision. Single-source reads can calculate a bounded price estimate for retained older text without saving it or creating work.",
        "parameters": [
          {
            "in": "path",
            "name": "call_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "target_language",
            "schema": {
              "enum": [
                "ar",
                "bn",
                "bg",
                "ca",
                "zh",
                "hr",
                "cs",
                "da",
                "nl",
                "en",
                "et",
                "fi",
                "fr",
                "de",
                "el",
                "gu",
                "he",
                "hi",
                "hu",
                "id",
                "it",
                "ja",
                "kn",
                "ko",
                "lv",
                "lt",
                "ms",
                "ml",
                "mr",
                "no",
                "fa",
                "pl",
                "pt",
                "ro",
                "ru",
                "sr",
                "si",
                "sk",
                "sl",
                "es",
                "sw",
                "sv",
                "ta",
                "te",
                "th",
                "tr",
                "uk",
                "ur",
                "vi"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "default": 200,
              "maximum": 500,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommunicationTranslation"
                }
              }
            },
            "description": "Canonical cached translation state. Originals remain readable on every failure."
          },
          "400": {
            "description": "Invalid target, limit, or cursor."
          },
          "404": {
            "description": "Source absent from the credential's own workspace, including for operator credentials."
          },
          "409": {
            "description": "Source changed; restart from the first page."
          }
        },
        "summary": "Read cached call translations",
        "x-required-scopes": [
          "recordings:read"
        ]
      },
      "post": {
        "description": "Explicitly request translation for any accessible settled source with retained usable text, regardless of its creation date, even when automatic agent translation is disabled. This enrolls only the requested source for revision recovery and never enables automatic translation of other historical sources. $0.02 per 1,000 original words needing translation per target, minimum $0.01 per successfully translated source/target. Matching-language-only text and cached reads cost zero. Failed attempts and platform corrections never double-charge. No funds leaves translation waiting_funds and does not resume calls or SMS.",
        "parameters": [
          {
            "in": "path",
            "name": "call_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TranslationRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommunicationTranslation"
                }
              }
            },
            "description": "Canonical cached translation state. Originals remain readable on every failure."
          },
          "400": {
            "description": "Invalid target or ineligible source."
          },
          "403": {
            "description": "Missing content/paid-write scope, suspended source or workspace."
          },
          "404": {
            "description": "Source not in the credential's workspace."
          }
        },
        "summary": "Request paid call translation",
        "x-required-scopes": [
          "recordings:read",
          "translations:write"
        ]
      }
    },
    "/v1/calls/{call_id}/waveform": {
      "get": {
        "responses": {
          "200": {
            "description": "Waveform"
          }
        },
        "summary": "Get waveform peaks for playback"
      }
    },
    "/v1/campaigns": {
      "get": {
        "parameters": [
          {
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Campaigns and pagination"
          }
        },
        "summary": "List campaigns (bounded page)",
        "x-required-scopes": [
          "calls:read"
        ]
      },
      "post": {
        "description": "One agent, one to ten caller-id numbers, one contact list, a purpose, a consent basis (existing_customer or explicit_consent), local calling windows (per contact timezone or one fixed zone), a per-disposition retry policy or preset (sales, collections, single_attempt), campaign concurrency within the plan's concurrent-call allowance, and an optional schedule. A campaign is locked at launch; duplicate it to change anything. Cold, purchased or scraped lists are not permitted.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Campaign"
          }
        },
        "summary": "Create a draft outbound campaign",
        "x-required-scopes": [
          "calls:write"
        ]
      }
    },
    "/v1/campaigns/{campaign_id}": {
      "get": {
        "parameters": [
          {
            "in": "path",
            "name": "campaign_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Campaign, including editable_fields for its status"
          }
        },
        "summary": "Read one campaign with its latest run and contact counters",
        "x-required-scopes": [
          "calls:read"
        ]
      },
      "patch": {
        "description": "A draft or needs_review campaign accepts every setting. A scheduled or paused campaign accepts the run-safe settings only: name, from_number_ids, time_zone_mode, time_zone, calling_windows, retry_policy / retry_policy_preset, max_concurrency, reserved_concurrency, ends_at and language, plus scheduled_start_at while still scheduled. Agent, contact list, purpose and consent basis are locked after launch (duplicate the campaign to change them); a running campaign must be paused first. The edit is serialized with the dispatcher and with pause/resume/cancel: a concurrent status change answers 409. Changing the calling windows or zone re-evaluates contacts that were waiting for the old window on the next tick. The response is the campaign plus `changed`, the list of settings that actually changed.",
        "parameters": [
          {
            "in": "path",
            "name": "campaign_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignUpdate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Campaign with changed[]"
          },
          "409": {
            "description": "Setting locked in this status, or the status changed concurrently"
          }
        },
        "summary": "Edit a campaign's settings",
        "x-required-scopes": [
          "calls:write"
        ]
      }
    },
    "/v1/campaigns/{campaign_id}/cancel": {
      "post": {
        "parameters": [
          {
            "in": "path",
            "name": "campaign_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Campaign"
          }
        },
        "summary": "Cancel a campaign: queued calls are cancelled, remaining contacts skipped",
        "x-required-scopes": [
          "calls:write"
        ]
      }
    },
    "/v1/campaigns/{campaign_id}/contacts": {
      "get": {
        "parameters": [
          {
            "in": "path",
            "name": "campaign_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "pending",
                "scheduled",
                "dispatched",
                "completed",
                "failed",
                "skipped",
                "pre_dial_failed",
                "do_not_call"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 500,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Campaign contacts and pagination"
          }
        },
        "summary": "List the latest run's contacts with status, disposition, attempts and next attempt (bounded page)",
        "x-required-scopes": [
          "calls:read"
        ]
      }
    },
    "/v1/campaigns/{campaign_id}/duplicate": {
      "post": {
        "parameters": [
          {
            "in": "path",
            "name": "campaign_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "contact_list_id": {
                    "type": "string"
                  },
                  "ends_at": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "scheduled_start_at": {
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": false
        },
        "responses": {
          "201": {
            "description": "Draft campaign"
          }
        },
        "summary": "Copy a campaign's configuration into a new draft",
        "x-required-scopes": [
          "calls:write"
        ]
      }
    },
    "/v1/campaigns/{campaign_id}/export": {
      "get": {
        "description": "One text/csv attachment with a fixed column set per contact: phone_e164, name, external_reference, language, time_zone, status, disposition, attempt_count, next_attempt_at, callback_at, skip_reason, last_call_id, last_call_status, termination_cause, termination_detail, updated_at, then the last call's business outcome: workflow_status, workflow_outcome_type (review_candidate, informational, terminal_note), workflow_outcome (the outcome step the run landed on), workflow_outcome_label, workflow_interrupt (wrong_party, cease_contact, dispute, hardship, callback, human_request, language_change), extraction_status (empty when no extraction was requested; pending, running, ready or failed), extraction_schema_version (the schema version the cells were extracted under; the column layout follows the agent's current schema), recording_available and transcript_available (true/false), and one extraction.<field> column per field of the agent's post-call extraction schema (advisory model output; empty unless extraction_status is ready). In list order, bounded by the contact-list cap (50,000 rows); X-Skysay-Row-Count carries the row count and X-Skysay-Truncated says whether rows beyond the cap were left out. Uploaded variables are not echoed back. Free-text cells that start with a formula character are prefixed; phone_e164 is written verbatim (import that column as text in a spreadsheet). Bound to the credential's own organization: an organization_id query value is ignored and a credential without an organization is refused.",
        "parameters": [
          {
            "in": "path",
            "name": "campaign_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "CSV file"
          }
        },
        "summary": "Export the latest run's results as CSV",
        "x-required-scopes": [
          "calls:read"
        ]
      }
    },
    "/v1/campaigns/{campaign_id}/launch": {
      "post": {
        "description": "Existing-customer or consented lists on active workspace capabilities start immediately (or at scheduled_start_at). The response carries the authorization decision: allowed, needs_review (workspace not active, voice not an enabled channel, a destination country outside the allowlist, or the custom implementation-services profile, whose every launch is reviewed) or blocked (sanctioned destination, blocked purpose, missing consent basis). Campaigns are open to every active workspace; there is no per-profile campaign switch. Contacts already on the do-not-call register are marked and never dialed.",
        "parameters": [
          {
            "in": "path",
            "name": "campaign_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Campaign with authorization and run"
          }
        },
        "summary": "Authorize and start a draft campaign",
        "x-required-scopes": [
          "calls:write"
        ]
      }
    },
    "/v1/campaigns/{campaign_id}/pause": {
      "post": {
        "parameters": [
          {
            "in": "path",
            "name": "campaign_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Campaign"
          }
        },
        "summary": "Pause a running campaign",
        "x-required-scopes": [
          "calls:write"
        ]
      }
    },
    "/v1/campaigns/{campaign_id}/results": {
      "get": {
        "description": "The contact page (status, disposition, attempts, next attempt, last call) with an `outcome` object per contact: workflow_status, workflow_outcome_type, workflow_outcome, workflow_outcome_label, workflow_interrupt, extraction_status, extraction_schema_version, extraction (the schema fields, advisory, present only when ready), recording_available, transcript_available. `extraction_fields` lists the agent's current extraction fields. A contact without a call carries the empty outcome. Fixed query budget whatever the page size.",
        "parameters": [
          {
            "in": "path",
            "name": "campaign_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "enum": [
                "pending",
                "scheduled",
                "dispatched",
                "completed",
                "failed",
                "skipped",
                "pre_dial_failed",
                "do_not_call"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 500,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Campaign contacts with outcomes, extraction_fields and pagination"
          }
        },
        "summary": "List the latest run's contacts with each contact's last-call business outcome (bounded page)",
        "x-required-scopes": [
          "calls:read"
        ]
      }
    },
    "/v1/campaigns/{campaign_id}/resume": {
      "post": {
        "parameters": [
          {
            "in": "path",
            "name": "campaign_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Campaign"
          }
        },
        "summary": "Resume a paused campaign",
        "x-required-scopes": [
          "calls:write"
        ]
      }
    },
    "/v1/campaigns/{campaign_id}/reviews": {
      "get": {
        "description": "Each item carries the attempt (attempt_no, call_id, disposition, termination_cause, settled_at), the contact (phone_e164, name, external_reference, language, status), the call's `outcome` evidence (workflow outcome and label, interrupt, extraction fields, recording and transcript availability) and its `resolution` (null while open; id, note, resolved_at, resolved_by once resolved). `counts` gives open and resolved totals, `outcomes` and `languages` the facets over the whole candidate set with counts, `extraction_fields` the agent's fields. Latest run only; newest first.",
        "parameters": [
          {
            "in": "path",
            "name": "campaign_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "state",
            "required": false,
            "schema": {
              "default": "open",
              "enum": [
                "open",
                "resolved",
                "all"
              ],
              "type": "string"
            }
          },
          {
            "description": "An outcome step id from `outcomes` (for example promise_review).",
            "in": "query",
            "name": "outcome",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "language",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Review candidates, counts, facets and pagination"
          }
        },
        "summary": "The campaign's review inbox: attempts whose workflow landed on a review_candidate outcome (bounded page)",
        "x-required-scopes": [
          "calls:read"
        ]
      }
    },
    "/v1/campaigns/{campaign_id}/reviews/export": {
      "get": {
        "description": "One text/csv attachment: phone_e164, name, external_reference, language, attempt_no, call_id, disposition, termination_cause, settled_at, the outcome columns of the results export, one extraction.<field> column per schema field, then resolved_at, resolved_by and note. `state` selects resolved (default), open or all. Bounded like the results export (X-Skysay-Row-Count, X-Skysay-Truncated); free-text cells are formula-guarded. Bound to the credential's own organization.",
        "parameters": [
          {
            "in": "path",
            "name": "campaign_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "state",
            "required": false,
            "schema": {
              "default": "resolved",
              "enum": [
                "open",
                "resolved",
                "all"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "description": "CSV file"
          }
        },
        "summary": "Export the review inbox (resolved by default) as CSV",
        "x-required-scopes": [
          "calls:read"
        ]
      }
    },
    "/v1/campaigns/{campaign_id}/reviews/{attempt_id}/resolve": {
      "post": {
        "description": "Records the resolution (note of at most 2,000 characters, the resolving user or API key, the time) and writes a `campaign.review.resolved` audit event with the campaign, call, workflow run and outcome. Returns the item as the inbox lists it, plus `requeue_requested`, `contact_requeued` and `requeue_refused_reason`.\n\nSend `requeue_contact: true` to put the contact back in the queue when you disagree with the outcome. Resolving dials nobody. The requeue is REFUSED, with `contact_requeued: false` and a `requeue_refused_reason`, when the number is on your do-not-call register or the contact is already marked `do_not_call` (`do_not_call_register`), when the contact has no readable number to check against that register (`contact_phone_unknown`), or when its campaign run can no longer be dispatched (`campaign_run_not_dispatchable`). A refusal is still a successful resolution: the note and the audit event are recorded either way.\n\nA candidate resolved before answers 409; an attempt that is not a review candidate of this campaign answers 404. The tenant is the campaign's own.",
        "parameters": [
          {
            "in": "path",
            "name": "campaign_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "attempt_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "note": {
                    "maxLength": 2000,
                    "type": "string"
                  },
                  "requeue_contact": {
                    "description": "Put the contact back in the queue. Refused, and reported as such, when the number is suppressed or its run cannot be dispatched.",
                    "type": "boolean"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": false
        },
        "responses": {
          "200": {
            "description": "The resolved review item, with `requeue_requested`, `contact_requeued` and `requeue_refused_reason`"
          },
          "400": {
            "description": "`requeue_contact` is not a boolean"
          },
          "404": {
            "description": "Not a review candidate of this campaign"
          },
          "409": {
            "description": "Already resolved"
          }
        },
        "summary": "Resolve one review candidate with a reviewer note",
        "x-required-scopes": [
          "calls:write"
        ]
      }
    },
    "/v1/contact-lists": {
      "get": {
        "parameters": [
          {
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Contact lists and pagination"
          }
        },
        "summary": "List contact lists in the workspace (bounded page)",
        "x-required-scopes": [
          "calls:read"
        ]
      },
      "post": {
        "description": "Rows carry phone_e164 plus optional name, language, time_zone (an IANA time zone, matched ignoring case and stored by its zone name; empty takes the number's zone when it has only one), external_reference, consent_basis and flat template variables (the attempt's whole call context: at most 32 keys and 4 KB, lists of at most 16 items; never secrets). Invalid rows are reported per row; duplicates within the list are skipped. At most 10,000 rows per request.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactListCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Contact list with an import report when rows were supplied"
          }
        },
        "summary": "Create a contact list, optionally importing rows in the same request",
        "x-required-scopes": [
          "calls:write"
        ]
      }
    },
    "/v1/contact-lists/{contact_list_id}": {
      "delete": {
        "description": "Refused (409, reason contact_list_in_use) while a campaign using the list is launching, scheduled, running or paused; the response names up to five of those campaigns (id, name, status) with total and has_more. Cancel them, or wait until they finish, then delete. Nothing is detached from a campaign. Otherwise the list is deleted: it no longer appears in any list, search, import, export, edit or new campaign, and a draft campaign that named it must pick another list before it can launch. Contacts no campaign run has taken are removed. Contacts a campaign run has taken are kept only as that campaign's records, so its contacts, results export and reviews read as before; they are no longer found as contacts. The response counts both: removed_contacts and kept_for_campaign_history.",
        "parameters": [
          {
            "in": "path",
            "name": "contact_list_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{id, deleted, removed_contacts, kept_for_campaign_history}"
          },
          "404": {
            "description": "No such list in this workspace"
          },
          "409": {
            "description": "A campaign that can still dial the list uses it: {reason, message, campaigns: [{id, name, status}], total, has_more}"
          }
        },
        "summary": "Delete a contact list",
        "x-required-scopes": [
          "calls:write"
        ]
      },
      "get": {
        "parameters": [
          {
            "in": "path",
            "name": "contact_list_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Contact list"
          }
        },
        "summary": "Read one contact list",
        "x-required-scopes": [
          "calls:read"
        ]
      },
      "patch": {
        "description": "Send name only: required, at most 120 characters, no control characters, trimmed. As when a list is created, two lists may share a name. Nothing else about the list or its contacts changes.",
        "parameters": [
          {
            "in": "path",
            "name": "contact_list_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactListUpdate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The renamed list"
          },
          "400": {
            "description": "The name is blank, too long or has control characters, or another field was sent"
          },
          "404": {
            "description": "No such list in this workspace"
          }
        },
        "summary": "Rename a contact list",
        "x-required-scopes": [
          "calls:write"
        ]
      }
    },
    "/v1/contact-lists/{contact_list_id}/contacts": {
      "get": {
        "parameters": [
          {
            "in": "path",
            "name": "contact_list_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 500,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Contacts and pagination"
          }
        },
        "summary": "List contacts in a list (bounded page in import order)",
        "x-required-scopes": [
          "calls:read"
        ]
      },
      "post": {
        "parameters": [
          {
            "in": "path",
            "name": "contact_list_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactImport"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Per-row import report"
          }
        },
        "summary": "Append rows to a contact list",
        "x-required-scopes": [
          "calls:write"
        ]
      }
    },
    "/v1/contact-lists/{contact_list_id}/contacts/{contact_id}": {
      "delete": {
        "description": "Removes the contact and lowers the list's contact count; other contacts keep their import order. A contact a campaign run has already taken cannot be deleted (409).",
        "parameters": [
          {
            "in": "path",
            "name": "contact_list_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "contact_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "{deleted, id}"
          },
          "404": {
            "description": "No such contact in this workspace"
          },
          "409": {
            "description": "A campaign run holds this contact"
          }
        },
        "summary": "Delete one contact",
        "x-required-scopes": [
          "calls:write"
        ]
      },
      "patch": {
        "description": "Send any of phone_e164, name, first_name, language, time_zone, variables, external_reference and consent_basis; the rest keep their values. The edited contact is validated exactly like an imported row. A contact keeps its place in the list's import order. A contact a campaign run has already taken can still be edited, but its number cannot change (409), and a number another contact in the list already has is refused (409).",
        "parameters": [
          {
            "in": "path",
            "name": "contact_list_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "contact_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactUpdate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "The contact as stored"
          },
          "404": {
            "description": "No such contact in this workspace"
          },
          "409": {
            "description": "The number cannot change or is already in the list"
          }
        },
        "summary": "Edit one contact",
        "x-required-scopes": [
          "calls:write"
        ]
      }
    },
    "/v1/contact-lists/{contact_list_id}/export": {
      "get": {
        "description": "Every contact in import order: phone_e164, name, first_name, language, time_zone, country, external_reference, consent_basis, then one variables.<name> column per custom value to read (a list value as JSON; at most 200 such columns), then a variables column with every custom value as typed JSON. Text a spreadsheet could read as a formula, or that starts with an apostrophe, is prefixed with one apostrophe. The dashboard's Import CSV reads such a file back as the same contacts, custom value types included, consent_basis too. The file streams in import order in chunks of 1,000 contacts, so it has no Content-Length (HTTP/1.1 clients get it chunked, and a download that fails partway fails rather than ending early); at most 50,000 contacts (the list limit), and X-Skysay-Truncated says when a list held more. X-Skysay-Row-Count is the number of contacts when the download started; a contact deleted while it downloads is left out, one added is not included. A pure read, bound to the credential's own workspace.",
        "parameters": [
          {
            "in": "path",
            "name": "contact_list_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "text/csv"
          },
          "404": {
            "description": "No such list in this workspace"
          }
        },
        "summary": "Download a contact list as CSV",
        "x-required-scopes": [
          "calls:read"
        ]
      }
    },
    "/v1/contacts": {
      "get": {
        "description": "A search that starts with + matches the start of the number; other digits match anywhere in the number; text matches anywhere in the name, first name or external reference, ignoring case. Results come in a stable order (number, list, contact) with has_more. An empty search returns no results.",
        "parameters": [
          {
            "in": "query",
            "name": "search",
            "required": true,
            "schema": {
              "maxLength": 80,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching contacts, each with its list, and pagination"
          }
        },
        "summary": "Find contacts by number or name across every list (bounded page)",
        "x-required-scopes": [
          "calls:read"
        ]
      }
    },
    "/v1/conversations": {
      "get": {
        "description": "One bounded page, newest first, as an object: the items under `conversations` and `pagination`. Follow `pagination.next_cursor` while `pagination.has_more` is true; a walk returns every row that exists for the whole walk exactly once. Ordered by creation, newest first. Each conversation carries `message_count` and its newest message as `last_message`, never its history: read that with GET /v1/messages?conversation_id=.",
        "parameters": [
          {
            "description": "Optional with a workspace key, which lists its own organization. An operator credential must name one: no list spans organizations.",
            "in": "query",
            "name": "organization_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only this project's rows. A project of another organization is a 400.",
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page size, 1 to 200; 50 when omitted. A value outside that range or not a whole number is a 400, never shortened.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The previous page's `pagination.next_cursor`, exactly as received. Anything else is a 400.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "conversations": {
                      "description": "At most `limit` items, at most 200.",
                      "items": {
                        "$ref": "#/components/schemas/ConversationSummary"
                      },
                      "type": "array"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/ListPagination"
                    }
                  },
                  "required": [
                    "conversations",
                    "pagination"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "One page. Server-Timing and X-Skysay-Query-Count describe the read; Cache-Control is no-store."
          },
          "400": {
            "description": "`limit` outside its range or missing where required, a `cursor` that is not a `next_cursor`, no organization for an operator credential, or a project of another organization."
          }
        },
        "summary": "List SMS conversations"
      }
    },
    "/v1/conversations/{conversation_id}": {
      "get": {
        "description": "The conversation as GET /v1/conversations lists it: `message_count` and `last_message`, never its history; read that with GET /v1/messages?conversation_id=.",
        "parameters": [
          {
            "in": "path",
            "name": "conversation_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationSummary"
                }
              }
            },
            "description": "Conversation"
          },
          "404": {
            "description": "Conversation not found"
          }
        },
        "summary": "Get one SMS conversation"
      }
    },
    "/v1/coverage-matrix": {
      "get": {
        "responses": {
          "200": {
            "description": "Coverage matrix"
          }
        },
        "summary": "List country coverage, messaging, simple onboarding terms, and safe activation speed"
      }
    },
    "/v1/customer-onboarding": {
      "get": {
        "description": "One bounded page, newest first, as an object: the items under `customer_onboarding` and `pagination`. Follow `pagination.next_cursor` while `pagination.has_more` is true; a walk returns every row that exists for the whole walk exactly once.",
        "parameters": [
          {
            "description": "Optional with a workspace key, which lists its own organization. An operator credential must name one: no list spans organizations.",
            "in": "query",
            "name": "organization_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page size, 1 to 200; 50 when omitted. A value outside that range or not a whole number is a 400, never shortened.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The previous page's `pagination.next_cursor`, exactly as received. Anything else is a 400.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "customer_onboarding": {
                      "description": "At most `limit` items, at most 200.",
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/ListPagination"
                    }
                  },
                  "required": [
                    "customer_onboarding",
                    "pagination"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "One page. Server-Timing and X-Skysay-Query-Count describe the read; Cache-Control is no-store."
          },
          "400": {
            "description": "`limit` outside its range or missing where required, a `cursor` that is not a `next_cursor`, no organization for an operator credential, or a project of another organization."
          }
        },
        "summary": "List business onboarding records"
      },
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomerOnboarding"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Onboarding record"
          }
        },
        "summary": "Submit business onboarding details"
      }
    },
    "/v1/dashboard/agent-config": {
      "get": {
        "description": "Returns one selected agent, its resolved stack including saved turn_taking, voice_polish, answer_detection, navigation and audio_environment settings, which components were actually configured (for a voice the agent never stored, voice_stack.language_profiles.default_voices names the default voice model of each supported language the shown voice cannot run, so a language added in the editor gets one too), the same inbound-admission verdict the Agents list carries, the runnable-adapter catalog (each entry carrying component_metered, so the launch-supported view is one filter over it rather than a second copy), a wallet projection, a bounded metadata-only BYOK credential list, and the agent's measured reply time: the median of a bounded sample of its recorded worker-side reply turns, reported as unavailable rather than zero until real calls exist. That figure ends when the first audio frame leaves the worker, so carrier transit and remote playout are not in it. It is bound to the dashboard session organization and selected project; it never decrypts credentials, calls a provider, validates a key, or writes state. It also carries `call_variables`: the AI agent's normalized call-variables config (declared fields, SMS templates, SMS mode, caller lookup), the `revision` a write names as `base_revision`, the `schema` its calls are admitted with (a published workflow's variables when one is active, with `workflow_version` and the agent's own earlier fields as `superseded_fields`), whether its voice engine is live, its languages, and at most 50 of the project's contact lists for caller lookup, newest first, with `lookup_lists_has_more`. It is written only through `PUT /v1/agents/{agent_id}/variables`. The editor also carries the AI agent's failure_breakdown over the calls it handled in the last 30 days (window_days): failure_breakdown counts the labelled calls (classified_calls), those with a failure (failed_calls), and the failures by layer (telephony, media, agent; only layers with a failure appear) and within each layer by category, most frequent first; a breakdown with no failure has an empty layers list. One grouped read; labels are never decided on this read. It also carries `capabilities`, what the AI agent's configured tools, attached knowledge bases and calendar tools do on each conversation engine (`engines`, the saved one in `engine`) and call type (`call_types`: `inbound`, `outbound`, `browser_test` for the agent page's browser test, and `homepage_demo` in the homepage demo workspace only). `calls[engine][call_type]` says whether such a call starts (`starts`), whether it runs the published workflow (`workflow`: outbound calls do; an inbound call only through a workflow phone test pin) and, when it does not start, `refused_reason`. `items` lists each configured tool (at most 40, with `items_has_more`), an implied built-in such as request_callback (`implied`), the knowledge search when a knowledge base is attached, and phone menu navigation, each with `id`, `kind` (`builtin`, `calendar`, `webhook`, `connection`, `legacy`, `knowledge`, `navigation`, `unknown`), `name` and `status[engine][call_type]`: null when the item is offered on such a call, else the one reason it is not. `knowledge` counts the attached active knowledge bases and whether one has a ready document. It describes what is offered, not what a call does: suspension, the wallet, outbound authorization and a tool's own endpoint are still decided per call. The reasons are a closed set: `engine_cannot_run_workflows`: this call type runs the AI agent's published workflow, and its conversation engine (GPT-Live and Gemini Live today) cannot run workflow calls, so such calls are refused before they start; `homepage_demo_refuses_workflows`: the homepage demo does not run an AI agent that has a published workflow; `tool_budget`: the tools do not fit the call's 32-name tool contract (on a billed call it counts end_call and a ready knowledge base too): the call is refused, or it runs without the implied built-in (request_callback) or without phone menu navigation's three tools; `tool_contract_unsafe`: a tool name cannot be part of a call's tool contract (at most 32 lowercase letters, digits and underscores, starting with a letter), so calls are refused; `workflow_call`: this call type runs the AI agent's published workflow, and workflow calls do not offer the AI agent's own tools, knowledge bases or calendar tools yet; `not_buildable`: the voice worker cannot declare this entry (an unknown type or built-in, a repeated or reserved name, or a schema that is not an object or nests deeper than 32 levels), so it is left out of the call and named in a configured_tool_omitted event; `sms_templates_missing`: the SMS mode is Templates only and the AI agent has no SMS template, so there is nothing send_sms could send; `integration_unavailable`: the integration action, or its integration, is not active in this project; `calendar_connection_inactive`: the calendar connection the calendar tools name is not active in this project; `calendar_fleet_not_ready`: not every running voice worker can run the calendar tools yet, so they are not offered; `knowledge_not_ready`: no attached knowledge base has a ready document yet; `needs_phone_caller`: this built-in acts on a phone call (such as a text, a transfer or a callback), and this call type has no phone number on the line; `calendar_test_writes_off`: a workspace browser test books only on a dedicated test calendar connection with test writes allowed; `outbound_only`: phone menu navigation runs only on calls the AI agent places; `worker_tool_name`: the voice worker adds a tool of this name itself on these calls (switch_language while Follow the caller is on), so the configured tool is not built and a configured_tool_shadowed_by_worker_tool event names it; and, on the navigation item only, the reasons `navigation.ineligible_reason` of the voice-stack response documents: `turned_off`, `not_released`, `engine_not_supported`, `languages_unreleased`, `languages_uncovered`, `too_many_sockets`, `no_language`, `tool_name_conflict`.",
        "parameters": [
          {
            "description": "Canonical agent ID in the authenticated project.",
            "in": "query",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Optional project in the authenticated organization.",
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Hosted-agent editor aggregate"
          },
          "404": {
            "description": "Agent is not in the authenticated workspace"
          }
        },
        "summary": "Read the bounded, customer-scoped hosted-agent editor aggregate",
        "x-required-scopes": [
          "account:read",
          "agents:read",
          "calls:read",
          "gateways:read",
          "wallet:read",
          "knowledge:read",
          "numbers:read"
        ]
      }
    },
    "/v1/dashboard/agent-create": {
      "get": {
        "description": "Returns one tenant-bound DTO for the hosted New-agent wizard. Start and Identity receive presets, the hosted language intersection, and the workspace wallet header \u2014 not existing agents, activity, numbers, credentials, catalogues, or the voice gallery. step=voice (and the retired step=review) adds the stack catalogue without embedded directory voices plus the same bounded gallery the editor embeds. Direct voice/pinned-voice links use that same one request. Pure read: no provider calls, writes, or secret retrieval.",
        "parameters": [
          {
            "description": "Optional project in the authenticated organization.",
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Current wizard step. voice and the retired review value include the voice-step catalogue and gallery.",
            "in": "query",
            "name": "step",
            "required": false,
            "schema": {
              "enum": [
                "starting-point",
                "identity",
                "voice",
                "review"
              ],
              "type": "string"
            }
          },
          {
            "description": "Pinned provider::model::voice_id so a library link remains in the bounded gallery.",
            "in": "query",
            "name": "voice",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Draft language used to filter the voice-step gallery.",
            "in": "query",
            "name": "language",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "New-agent wizard aggregate"
          }
        },
        "summary": "Read the bounded New-agent wizard aggregate",
        "x-required-scopes": [
          "account:read",
          "agents:read",
          "wallet:read"
        ]
      }
    },
    "/v1/dashboard/agent-simulation-suite": {
      "get": {
        "description": "Authored scenarios, suite policy, bounded run history, and workflow readiness for the suite's selected snapshot source. Pure read: it never repairs a suite, queues a run, or calls a provider.",
        "parameters": [
          {
            "in": "query",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "suite_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Suite editor aggregate"
          },
          "404": {
            "description": "Suite not found, or Simulations Lab is not enabled"
          }
        },
        "summary": "Read one suite's editor projection",
        "x-required-scopes": [
          "account:read",
          "agents:read"
        ]
      }
    },
    "/v1/dashboard/agent-simulation-suite-run": {
      "get": {
        "description": "The run's pinned identity, its per-scenario rollup, and one stably ordered page of attempts with an explicit has_more. Attempts are keyset-paginated so the query count stays constant as a suite is re-run. Also returns linkable_sessions: this AI agent's most recent Talk sessions and matched phone-test sessions that can be linked to an attempt as human evidence (newest first, at most 20, with linkable_sessions_page.has_more), carrying only the session id, kind, status, turn count and timestamps. Never returns the run's idempotency key, request fingerprint or pinned contract. Without recordings:read, attempt transcript, recording_ref and facts are omitted, assertion actual values and rubric rationale are omitted, and linked evidence omits notes. Status, verdicts and pagination remain available at the base scope, identically for API keys and OAuth MCP callers.",
        "parameters": [
          {
            "in": "query",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "run_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 50,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Run results aggregate"
          },
          "404": {
            "description": "Run not found, or Simulations Lab is not enabled"
          }
        },
        "summary": "Read one run with a bounded page of attempt results",
        "x-required-scopes": [
          "account:read",
          "agents:read"
        ]
      }
    },
    "/v1/dashboard/agent-simulation-suites": {
      "get": {
        "description": "The same canonical suite rows the workspace index returns, already filtered to one agent. Not a second suite system and not a copy.",
        "parameters": [
          {
            "in": "query",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 50,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Agent-scoped suite index"
          },
          "404": {
            "description": "Agent not found, or Simulations Lab is not enabled"
          }
        },
        "summary": "Read one AI agent's slice of the same suite records",
        "x-required-scopes": [
          "account:read",
          "agents:read"
        ]
      }
    },
    "/v1/dashboard/agent-workflow": {
      "get": {
        "description": "Returns one selected agent's workflow draft (with a live, in-memory validation summary), the currently published version summary if any, and a bounded version history. Distinguishes an agent with zero workflows (workflow: null) from an existing draft. It also carries `call_variables`: `{\"active\": false}` until call variables are switched on, and then whether the AI agent looks callers up in a contact list (`caller_lookup`), which the draft's validation uses to walk protected values exactly as the draft update's response does. It is bound to the dashboard session organization and selected project; it never compiles server-side for a side effect, calls a provider, or performs a write.",
        "parameters": [
          {
            "description": "Canonical agent ID in the authenticated project.",
            "in": "query",
            "name": "agent_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Optional project in the authenticated organization.",
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Workflow builder aggregate"
          },
          "404": {
            "description": "Agent is not in the authenticated workspace"
          }
        },
        "summary": "Read the bounded, customer-scoped Workflow builder aggregate",
        "x-required-scopes": [
          "account:read",
          "agents:read",
          "wallet:read"
        ]
      }
    },
    "/v1/dashboard/agents": {
      "get": {
        "description": "Returns one tenant-bound Agents DTO for SSR. It includes resolved static voice-stack display data, which components the customer actually configured (as opposed to the catalog defaults the resolved stack fills in), every language each agent answers in (`selected_languages`, which is the agent's own `language` alone for an agent whose owner never chose a language set -- `language` remains the one that OPENS the call), an inbound-admission verdict per agent, attached number routing, exact UTC-day activity, each agent's measured reply time (the median of its recorded worker-side reply turns, or an explicit unavailable when nothing was measured -- never a component-constant estimate presented as an observation), and wallet currency without provider or catalog API calls, verification, invoice, SMS, or webhook service reads. The admission verdict is a pure read of workspace state: `admits`/`readiness` reflect only conditions the inbound call path itself would refuse over; `blockers` is not a strict subset of those -- a small, explicitly declared set of codes (currently a misconfigured transfer_to_human destination) is real and actionable but does not affect `admits`/`readiness`, because invoking that tool mid-call fails gracefully rather than refusing the call. `unevaluated` lists the conditions the read could not evaluate before a call exists, and `readiness` is blocked or indeterminate \u2014 never ready, because the gates that need the agent's number are not run here. A line-scoped readiness is reported by the Overview aggregate's primary_line. An agent answered by the customer's own service is judged on the endpoints it needs rather than on the managed stack it does not use. Soft-deleted agents are not part of the workspace: they are excluded from `agents` and from `total_agents`.",
        "responses": {
          "200": {
            "description": "Agents workspace aggregate"
          }
        },
        "summary": "Read the bounded, customer-scoped Agents aggregate"
      }
    },
    "/v1/dashboard/api-keys": {
      "get": {
        "description": "Returns API-key metadata only; key material and hashes are never returned. `connected_apps` lists the apps connected to the workspace over OAuth (`grants`, newest first, at most 50, with `has_more`); `enabled` is false, with an empty list, while OAuth sign-in is not available.",
        "responses": {
          "200": {
            "description": "API key workspace aggregate"
          }
        },
        "summary": "Read the bounded, customer-scoped API key aggregate"
      }
    },
    "/v1/dashboard/billing": {
      "get": {
        "description": "Returns one read-only Billing DTO for SSR and freshness polling, including workspace-wide number_allowance (used, limit, remaining, can_add; null limit means no billed-number cap). Wallet available_minor is purchased funds; promotional_available_minor is browser-only credit and total_available_minor is their combined amount. The aggregate is tenant-bound to the bearer session, uses no Stripe or carrier request, and bounds recent wallet activity and receipts.",
        "parameters": [
          {
            "description": "Optional exact request in the authenticated workspace project; an invalid context fails instead of selecting another request. Requires numbers:read in addition to account:read, usage:read and wallet:read when present.",
            "in": "query",
            "name": "number_request_id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Billing workspace aggregate"
          }
        },
        "summary": "Read the bounded, customer-scoped Billing aggregate"
      }
    },
    "/v1/dashboard/billing-history": {
      "get": {
        "description": "Returns one read-only page DTO. `history` is wallet spend per UTC period (granularity `day` with periods up to 90, default 30; or `month` with periods up to 24, default 12; `end` optionally names the last day YYYY-MM-DD or month YYYY-MM), per category (`calls`, `sms`, `numbers`, `recordings`, `translations`, `other`), in the wallet's currency as integer minor units, with every period present. Spend is money the ledger recorded as spent; top-ups, refunds and call holds/releases are not spend. `ledger` is one page of wallet entries, newest first by (created_at, id), filtered by `type` (`all`, `topup`, `call`, `sms`, `number`, `recording`, `translation`, `refund`, `hold`, `other`) and an inclusive UTC date range `from`/`to` (YYYY-MM-DD, default the history window); pass `next_cursor` back as `cursor` while `has_more` is true. `matching_count` counts the filtered entries up to `export_row_cap`, and `matching_count_exceeds_export_cap` says the CSV export would be refused. `calls_split` divides the range's call spend into `phone_line_minor` (the carrier component's share of each hosted AI call, rounded down, or the whole of a call billed without hosted AI components) and `ai_agent_minor` (the rest); the two always add up to `calls_minor`. Each entry's `amount_minor` is how the entry reads to a customer: the amount spent, negative, for a charge (which is taken from an earlier hold), otherwise the change to available funds (a hold negative, its release positive, a credit positive); so amounts across a call's hold, charge and release do not sum to the balance change. `available_delta_minor` sums to the change in available funds and `spent_delta_minor` to spend. Entries carry only customer-safe fields: Stripe references in short form, a `call_id` only for a call in the same workspace, and an `sms_conversation_id` for an SMS charge. The ledger lists the whole organization's wallet, every project's entries alike, whatever `project_id` is passed (a `project_id` must belong to the session's organization, else 404). Each entry's `project_id` is the project of its call or SMS conversation (null when it has neither, or the conversation belongs to no project). The dashboard Calls and SMS pages read only the session's project, so the workspace links an entry's call or conversation only when the entry's `project_id` is that project. Tenant-bound to the bearer session; no provider request and no write.",
        "parameters": [
          {
            "in": "query",
            "name": "granularity",
            "required": false,
            "schema": {
              "default": "day",
              "enum": [
                "day",
                "month"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "periods",
            "required": false,
            "schema": {
              "maximum": 90,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "end",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "type",
            "required": false,
            "schema": {
              "default": "all",
              "enum": [
                "all",
                "topup",
                "call",
                "sms",
                "number",
                "recording",
                "translation",
                "refund",
                "hold",
                "other"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "from",
            "required": false,
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "to",
            "required": false,
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Billing history aggregate"
          },
          "400": {
            "description": "A range, filter or cursor is out of bounds or malformed"
          },
          "404": {
            "description": "The named project is not in the session's organization"
          }
        },
        "summary": "Read the Billing history aggregate: usage by day or month and one page of the wallet ledger"
      }
    },
    "/v1/dashboard/billing-history/export": {
      "get": {
        "description": "Returns exactly the entries the Billing history ledger pages through for the same `type`, `from` and `to` (both required, YYYY-MM-DD, UTC, inclusive), in the same order, as `text/csv`. Columns: created_at, id, type, category, currency, amount (decimal), amount_minor, available_delta_minor, reserved_delta_minor, spent_delta_minor, promotional_spent_delta_minor, reference, call_id, sms_conversation_id, note. Integer minor-unit columns are the exact figures; amount_minor has the aggregate's meaning, available_delta_minor sums to the change in available funds and spent_delta_minor to spend. A file carries at most 10,000 entries: a larger selection is refused with 400 and a message asking for a shorter range, never truncated.",
        "parameters": [
          {
            "in": "query",
            "name": "type",
            "required": false,
            "schema": {
              "default": "all",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "from",
            "required": true,
            "schema": {
              "format": "date",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "to",
            "required": true,
            "schema": {
              "format": "date",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "text/csv": {}
            },
            "description": "Wallet ledger CSV"
          },
          "400": {
            "description": "Missing or invalid range, or more than 10,000 matching entries"
          }
        },
        "summary": "Export the filtered wallet ledger as CSV"
      }
    },
    "/v1/dashboard/bootstrap": {
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DashboardBootstrap"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Linked dashboard user, organization, project, membership, and session token"
          }
        },
        "summary": "Internal Auth.js-to-control-plane bootstrap for human dashboard sessions"
      }
    },
    "/v1/dashboard/calls": {
      "get": {
        "description": "Public customer API for the Calls workspace view: the newest page of call history with its real total, the selected call's observability detail, the workspace agents, the latest trial, and the compact wallet snapshot the shared workspace header uses. It also reports, per call, which workflow version ran and a compact path through it -- step ids, chosen routes, tool statuses and sanitized policy reasons -- plus the full turn-grouped trace for the selected call. A selected transcript turn's has_audio flag is true only while an active recording and its linked turn clip are available; clients use the authenticated recording relay rather than a storage or artifact identifier. A call that ran no workflow is ABSENT from workflow_evidence rather than present and empty. The selected call's transcript also carries one server-ordered timeline interleaving its turns with the typed tool calls the agent finished, the language switches it made and, on a call that navigated automated phone menus, the menus it heard, the keys it pressed (a value it typed is named by its call variable and never shown as digits), the waits on hold and the moment a person answered, ordered on the recording clock with a single truncation flag for both sources; turn entries reference the turns returned beside them rather than repeating their text. Every call row carries `navigation`: null unless the call recorded a phone-menu moment, else {menu_ms, hold_ms, person_at_ms, conversation_ms}, how the call's time split into time in menus, time on hold (a hold with no recorded end runs to the end of the call), the offset at which a person first answered (null when none did) and the time that followed. The selected call's waveform carries at most 256 peaks (a longer stored series is max-pooled, never cut) and speaker_segments: who spoke when on the recording's own clock, each with turn_id, speaker (agent or caller), start_ms and end_ms, projected only from turns whose boundary was measured on that recording, bounded by the same turn slice and reported with speaker_segments_truncated; a moment no segment covers is unattributed, not silent. The selected call's counterparty carries the other person's phone_e164 (the number called, or the caller's number on an inbound call; empty for a browser test) and contact_name, the name that number is saved under in this organization's contacts (the newest when several lists hold it; empty when none). Those rows are typed only: a tool entry carries its closed kind, an opaque reference, ok, a closed error class and a duration, and the configured tool name is resolved for display from the call's own agent configuration rather than stored - tool arguments, results, URLs, response bodies and error messages are never recorded. Each capability reports its own status and a closed reason, so a call handled before this existed, a gateway-imported call, a workflow call or an engine that does not record language switches is explicit about it and never reads as a call that made no tool calls. Bound to the bearer session organization; it never accepts an arbitrary organization, calls a provider, reconciles state, or writes data. The call's internal runtime configuration is never returned, and neither is a run's pinned contract. Both the path per row and the trace are bounded and report truncation; detail=0 omits the selected call's observability and its trace for routine freshness polls. Every response echoes calls_source_kind, the provenance it actually answered for, and calls_has_more, which says whether an older page exists -- observed by reading one row past the limit, never inferred from a page that happens to be full. Each call row carries source_kind, from the same closed set as the query parameter. The selected call's observability carries call_variables and its trace carries variables_evidence; call_variables lists what admission froze for the call -- each declared variable and built-in with its name, type, source (builtin, or the call's source: api, campaign, inbound_lookup, browser_test, simulation, trial, homepage_demo), protected, requires and state -- plus the committed confirmation record (method, match_kind, step_id, step_name, turn_index, committed_at), bounded with limit, returned and has_more; state is ready, not_admitted (no snapshot) or unreadable. A value is returned only for an ordinary variable in state shown; a protected variable never carries a value, before or after confirmation, only its state: held (until a confirmation releases it) or released with released_at_turn. variables_evidence lists the run's knowledge_match outcomes (match, mismatch, abstain) and workflow SMS-step statuses (pending, sending, sent, failed, uncertain, recorded) in run order from one bounded read, with limit, returned and has_more; never an answer, a compared value, an SMS body, a recipient or a failure text. Each call row carries failure: null until the finished call has been labelled, otherwise why it failed -- category (one closed code, empty when no failure was observed, which is not a proof the call went well), layer (telephony: it never became a conversation; media: the voice pipeline broke; agent: the AI agent or the customer's own systems failed), signals (every code observed, in precedence order), connected, and evidence_complete (whether the transcript and telemetry were complete; signals that rest on them are only derived when it is true). Labels are decided when the call ends and again as its evidence arrives; this read never decides one. failure_layer or failure_category narrows the page, calls_total and has_more to the labelled calls that match, and calls_failure_filter echoes the filter applied; a call named by call_id is still answered when it does not match.",
        "parameters": [
          {
            "description": "Optional project in the authenticated organization.",
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Optional canonical call ID in the selected project; it is resolved and pinned even when older than the newest page. A call_id that is not in the authenticated organization, the selected project and the requested source_kind is answered with selected_call_not_found=true, an empty selected_call_id and no selected call or detail -- never with a different call in its place; the page of calls is returned as usual. Without call_id the newest call is selected.",
            "in": "query",
            "name": "call_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Maximum calls in the returned page.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 100,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Set to 0 for a freshness poll: the selected call's observability and full workflow trace are omitted, the per-row workflow evidence is not.",
            "in": "query",
            "name": "detail",
            "required": false,
            "schema": {
              "default": "1",
              "enum": [
                "0",
                "1"
              ],
              "type": "string"
            }
          },
          {
            "description": "Which calls the page is about. `live` -- the default, and what this route has always returned -- is a call a customer or a caller actually made. `simulation_suite` is a Simulations Lab call: a real call row with the customer's own agent in it and a synthetic caller on the other end, created by the lab to run a scenario. The two are separate views of the same page, never mixed: one request answers for one provenance, and every figure in the response -- the page, calls_total, the selected call and the workflow evidence -- is about that provenance alone. A value outside this set is a 400 rather than a silent fall back to `live`.",
            "in": "query",
            "name": "source_kind",
            "required": false,
            "schema": {
              "default": "live",
              "enum": [
                "live",
                "simulation_suite"
              ],
              "type": "string"
            }
          },
          {
            "description": "A `page_cursor` from a row of a previous page. The response continues strictly older than that ROW, so calls created since do not shift the page under you the way an offset would. Opaque: its encoding is not part of this contract.",
            "in": "query",
            "name": "before",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only calls labelled with a failure in this layer. A value outside the set is a 400.",
            "in": "query",
            "name": "failure_layer",
            "required": false,
            "schema": {
              "enum": [
                "telephony",
                "media",
                "agent"
              ],
              "type": "string"
            }
          },
          {
            "description": "Only calls whose primary failure label is this category. A value outside the set, or one outside failure_layer when both are given, is a 400.",
            "in": "query",
            "name": "failure_category",
            "required": false,
            "schema": {
              "enum": [
                "telephony_inbound_rejected",
                "telephony_invalid_number",
                "telephony_carrier_rejected",
                "telephony_busy",
                "telephony_no_answer",
                "telephony_non_human_answer",
                "telephony_hold_limit",
                "telephony_menu_no_path",
                "telephony_menu_time_limit",
                "telephony_technical_failure",
                "media_session_lost",
                "media_engine_lost",
                "media_response_stall",
                "media_caller_speech_missed",
                "media_agent_silent",
                "media_stt_stall",
                "media_model_failure",
                "media_synthesis_failure",
                "media_truncated_goodbye",
                "agent_bad_handoff",
                "agent_sms_send_failed",
                "agent_tool_failure"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Calls workspace aggregate"
          }
        },
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "summary": "Read the bounded, customer-scoped Calls aggregate",
        "x-required-scopes": [
          "account:read",
          "calls:read",
          "recordings:read",
          "agents:read",
          "knowledge:read",
          "trial_calls:read",
          "wallet:read"
        ]
      }
    },
    "/v1/dashboard/calls/performance": {
      "get": {
        "description": "Public customer API for the Calls Performance workspace view. It is bound to the bearer session organization; it never accepts an arbitrary organization, calls a provider, parses object storage, reconciles state, or writes data. It includes the compact wallet snapshot used by the shared workspace header, but no billing history. Pass call_id only for a call in the selected project. Existing calls without a normalized call-telemetry artifact return evidence.status=unavailable and no fabricated spans. The bounded spans collection is ordered by turn, offset, stage, and attempt; audit correlation IDs, raw prompts, queries, passages, and secrets are never returned.",
        "parameters": [
          {
            "description": "Optional project in the authenticated organization.",
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Optional canonical call ID in the selected project; latest scoped call is used when omitted.",
            "in": "query",
            "name": "call_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Maximum normalized spans to return. The default matches the bounded source artifact, so every retained span is returned; has_more still reports defensive truncation.",
            "in": "query",
            "name": "span_limit",
            "required": false,
            "schema": {
              "default": 256,
              "maximum": 256,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallPerformanceDashboard"
                }
              }
            },
            "description": "Calls Performance workspace aggregate"
          },
          "404": {
            "description": "Requested call is not in the authenticated workspace"
          }
        },
        "security": [
          {
            "ApiKeyBearer": []
          }
        ],
        "summary": "Read the bounded, customer-scoped Calls Performance aggregate",
        "x-required-scopes": [
          "calls:read",
          "wallet:read"
        ]
      }
    },
    "/v1/dashboard/campaign-reviews": {
      "get": {
        "description": "Pure read: the campaign header (id, name, status, latest run), one filtered page of the campaign's review candidates with inline call evidence and resolutions, the counts and facets, and the compact wallet snapshot. `campaign` is null when the id is not in the session workspace (or the page's project). Fixed query budget; never a caller-selected tenant.",
        "parameters": [
          {
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "campaign_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "state",
            "required": false,
            "schema": {
              "enum": [
                "open",
                "resolved",
                "all"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "outcome",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "language",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Review inbox page projection"
          }
        },
        "summary": "Bounded review-inbox page aggregate for the signed-in workspace"
      }
    },
    "/v1/dashboard/campaigns": {
      "get": {
        "description": "Pure read: campaign list with latest run and counters, the selected campaign's summary (pick-up rate, disposition mix, spend) and one contact page, and the wizard reference data (agents, numbers, contact lists, retry presets, default windows, plan concurrency, and `launch_gates`: the workspace's record state, voice channel, destination allowlist, the sanctioned-country and blocked-purpose sets, the consent bases, whether every launch is reviewed, and the effective velocity caps -- what the wizard's review step previews a launch against; launch remains the authority). With `preview_agent_id` (an AI agent in the session workspace) the page also carries `agent_preview`: what that AI agent will say, rendered by the same preview the Contacts list detail serves (its DTO unchanged) for the first contact of `preview_list_id` by import order in the wizard's `preview_language` (a contact's own language still wins, as on the call), or state no_contact without a list and no_agent for an AI agent outside the workspace. The wizard sends one such request per AI agent selection its agent step shows. `agent_preview` is null when no AI agent is named. The same request carries `agent_opening`, how that AI agent's calls open, read from the publication a call freezes (its own settings while unpublished): `first_speaker` (`callee` or `agent`), `silence_wait_seconds`, `kind` (`literal`, `generated` or `workflow`), `line` (the written opening for `literal`, else empty), `published`, `language`, and `waits_without_line`, true when the AI agent waits for the callee and has no opening line, so a call where the person also waits can stay silent. A campaign has no opening of its own. `agent_opening` is null when no AI agent is named or it is outside the workspace. The selected campaign's summary carries the latest run's failure_breakdown, over the calls its dispatcher placed: failure_breakdown counts the labelled calls (classified_calls), those with a failure (failed_calls), and the failures by layer (telephony, media, agent; only layers with a failure appear) and within each layer by category, most frequent first; a breakdown with no failure has an empty layers list. One grouped read; labels are never decided on this read. Fixed query budget; never a caller-selected tenant.",
        "parameters": [
          {
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "campaign_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "contact_status",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "preview_agent_id",
            "required": false,
            "schema": {
              "maxLength": 120,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "preview_list_id",
            "required": false,
            "schema": {
              "maxLength": 120,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "preview_language",
            "required": false,
            "schema": {
              "maxLength": 35,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "minimum": 0,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "contacts_limit",
            "required": false,
            "schema": {
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "contacts_offset",
            "required": false,
            "schema": {
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Campaigns page projection"
          }
        },
        "summary": "Bounded Campaigns page aggregate for the signed-in workspace"
      }
    },
    "/v1/dashboard/capabilities": {
      "get": {
        "description": "Returns one read-only Capabilities page DTO: the workspace's capabilities record (status, approved profiles, channels, all_countries/countries, calling rules and whether they are active), the caps that bind right now as `limits` (the workspace's velocity and spend risk controls above zero, not the profile caps projected at activation; a cap of zero is disabled and not listed), live usage against the caps, the use-case profile catalog a request offers, `campaigns` (whether campaigns are part of what this workspace may do: `included` for an active record with voice, `voice_required`, or `not_active`; and `every_launch_reviewed` for the custom implementation-services profile -- there is no per-profile campaign switch), the countries the workspace already has numbers in (bounded, with has_more), and its open capability reviews (bounded, with has_more). Tenant-bound to the bearer session; no provider request and no write.",
        "responses": {
          "200": {
            "description": "Capabilities workspace aggregate"
          }
        },
        "summary": "Read the bounded, customer-scoped Capabilities aggregate"
      }
    },
    "/v1/dashboard/contacts": {
      "get": {
        "description": "Pure read: a page of contact lists (newest change first), each with its contact count, the custom value names seen in its first rows (custom_values.sampled_rows says how many), the campaigns that use it (at most five, with a total) and when it changed; plus the wallet header. Fixed query budget; never a caller-selected tenant.",
        "parameters": [
          {
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "search",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Contact lists page projection"
          }
        },
        "summary": "Bounded Contacts page (Lists tab) aggregate for the signed-in workspace"
      }
    },
    "/v1/dashboard/contacts/lists/{contact_list_id}": {
      "get": {
        "description": "Pure read: one list's contacts in import order, searched and filtered (all, not_called, reached, do_not_call), each with its first name, latest campaign outcome and do-not-call state. When a campaign uses the list (and its AI agent is hosted; an AI agent on the customer's own endpoint previews outcome external), a preview of what that campaign's AI agent says to the selected contact. For an AI agent with call variables set up (mode call_variables), the greeting, the script's opening and personalised lines and the SMS templates are rendered from this contact's values by the call-variables admission and template renderer, with the template's fallback wording where a value is missing (missing_values names them); when this contact's values would refuse the call (a required value missing, a wrong type), outcome is refused with the reason code, the field and the destination, never a value. For any other AI agent (mode as_written) the lines are shown as its calls speak them today. Fixed query budget; no provider or model call.",
        "parameters": [
          {
            "in": "path",
            "name": "contact_list_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "search",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "filter",
            "required": false,
            "schema": {
              "enum": [
                "all",
                "not_called",
                "reached",
                "do_not_call"
              ],
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "contact_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "campaign_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List detail projection"
          }
        },
        "summary": "Bounded Contacts page (list detail) aggregate"
      }
    },
    "/v1/dashboard/contacts/people": {
      "get": {
        "description": "Pure read: contacts matching a number or name across every list and, for the chosen phone, that person's lists, do-not-call entry, most recent calls and SMS threads, each bounded with a has_more flag. Fixed query budget.",
        "parameters": [
          {
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "search",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "phone",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 50,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "People search and person projection"
          }
        },
        "summary": "Bounded Contacts page (All contacts tab) aggregate"
      }
    },
    "/v1/dashboard/do-not-call": {
      "get": {
        "description": "Pure read: the workspace's do-not-call register, newest first, searched by number, with each entry's source and reason. Fixed query budget.",
        "parameters": [
          {
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "search",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Do-not-call register projection"
          }
        },
        "summary": "Bounded Contacts page (Do-not-call tab) aggregate"
      }
    },
    "/v1/dashboard/integrations": {
      "get": {
        "description": "Returns one tenant-bound, read-only DTO for customer Integrations, their bounded action metadata, hosted-agent attachments, and post-call extraction configuration. It never decrypts a signing secret, calls a customer endpoint, invokes a carrier, or returns endpoint URLs, secret material, arbitrary action arguments, or provider diagnostics.",
        "parameters": [
          {
            "description": "Optional project in the authenticated organization.",
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Integrations workspace aggregate"
          },
          "404": {
            "description": "Project is not in the authenticated workspace"
          }
        },
        "summary": "Read the bounded, customer-scoped Integrations aggregate",
        "x-required-scopes": [
          "account:read",
          "agents:read",
          "wallet:read"
        ]
      }
    },
    "/v1/dashboard/knowledge": {
      "get": {
        "description": "Returns one tenant-bound, read-only DTO for the Knowledge Bases page. It includes only lifecycle-safe base/document metadata and hosted-agent attachment ids; it never calls storage or a model, returns document bytes, passages, retrieval queries, provider credentials, or an unbounded document history.",
        "responses": {
          "200": {
            "description": "Knowledge Bases workspace aggregate"
          }
        },
        "summary": "Read the bounded, customer-scoped Knowledge Bases aggregate"
      }
    },
    "/v1/dashboard/me": {
      "get": {
        "responses": {
          "200": {
            "description": "Dashboard principal"
          }
        },
        "summary": "Read the current dashboard principal, workspace, project, membership, and wallet"
      }
    },
    "/v1/dashboard/numbers": {
      "get": {
        "description": "Returns the read-only data needed by the Numbers dashboard, including workspace-wide number_allowance (used, limit, remaining, can_add; null limit means no billed-number cap). Each number includes calling_readiness: bounded inbound routing/pricing blockers and destination-dependent outbound configuration. Outbound registration_status distinguishes pending, failed, registered, not_required and legacy; pending or failed caller-ID registration blocks outbound only and is retried independently of carrier-ready inbound activation. DIDWW capacity pricing can report capacity_hybrid_unsupported, capacity_separate_metering_unsupported, or capacity_plan_unavailable; these refuse only the affected number. Inbound readiness remains indeterminate when mandatory call-time admission checks were not evaluated; a purchased number is not proof that calls can connect. The aggregate is tenant-bound to the bearer session, makes no provider calls or writes, and excludes provider internals, secrets, unrelated billing history, calls, SMS, and verification documents. An exact request return may include the most recent confirmed positive Stripe top-up summary for conversion capture. Pass project_id for an explicit project; when omitted, the response returns its selected default project. Bounded collections expose truncation flags rather than silently implying a complete export.",
        "parameters": [
          {
            "description": "Optional exact request in the authenticated workspace project. An unavailable selection returns no selected request so the bounded Numbers list remains usable; never selects another request.",
            "in": "query",
            "name": "number_request_id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Numbers workspace aggregate"
          }
        },
        "summary": "Read the bounded, customer-scoped Numbers aggregate"
      }
    },
    "/v1/dashboard/onboarding": {
      "get": {
        "description": "Returns compact setup-flow state for onboarding SSR. It is tenant-bound, read-only, and excludes provider reconciliation and unbounded history feeds.",
        "responses": {
          "200": {
            "description": "Onboarding workspace aggregate"
          }
        },
        "summary": "Read the bounded, customer-scoped setup aggregate"
      }
    },
    "/v1/dashboard/overview": {
      "get": {
        "description": "Returns one read-only workspace DTO for Overview SSR and freshness polling. Call rows include the compact component_billing wallet verdict used by Calls, without billing line items. Each number carries incoming_rule -- what that line does when it is called -- resolved for every number in one batched read, so the page can say which lines answer without a request per number. knowledge reports how many active knowledge bases the workspace has and how many documents are in them, as two counts rather than the bases themselves. The aggregate is tenant-bound to the bearer session, uses no provider calls, and omits provider internals, full verification profiles, secrets, invoices, and voice-stack configuration.",
        "responses": {
          "200": {
            "description": "Overview workspace aggregate"
          }
        },
        "summary": "Read the bounded, customer-scoped Overview aggregate"
      }
    },
    "/v1/dashboard/settings": {
      "get": {
        "description": "Returns the signed-in workspace's project, gateway deployments, phone servers, and static setup data in one read-only response. It does not call a provider or expose credentials.",
        "responses": {
          "200": {
            "description": "Settings workspace aggregate"
          }
        },
        "summary": "Read the bounded, customer-scoped Settings aggregate"
      }
    },
    "/v1/dashboard/simulation-suites": {
      "get": {
        "description": "One tenant-bound, pure-read DTO for the workspace Simulations Lab page: every suite in the selected project across that project's AI agents, with its latest-run health, plus a bounded agent filter. Omitting project_id selects the workspace's first project rather than merging every project. Latest health is one batch join, never a lookup per suite, so the query count is fixed by the page rather than by how many suites the workspace owns. A suite that has never run reports never_run rather than an invented 0%. The page is bounded with an opaque cursor and an explicit has_more. Returns 404 while Simulations Lab is not enabled.",
        "parameters": [
          {
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Filter to one AI agent in this workspace.",
            "in": "query",
            "name": "agent_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 50,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Workspace suite index"
          },
          "404": {
            "description": "Simulations Lab is not enabled, or the filtered agent is not in this workspace"
          }
        },
        "summary": "Read the workspace-wide Simulations Lab index",
        "x-required-scopes": [
          "account:read",
          "agents:read"
        ]
      }
    },
    "/v1/dashboard/team": {
      "get": {
        "description": "Returns the signed-in workspace's members and current membership in one read-only response.",
        "responses": {
          "200": {
            "description": "Team workspace aggregate"
          }
        },
        "summary": "Read the bounded, customer-scoped Team aggregate"
      }
    },
    "/v1/dashboard/verification": {
      "get": {
        "description": "Returns the selected verification profile, current number-type delta, and a batched summary of available number types. It is tenant-bound, read-only, and never syncs carrier data during a page load.",
        "parameters": [
          {
            "description": "Optional exact request in the authenticated workspace project; an invalid context fails instead of selecting another request.",
            "in": "query",
            "name": "number_request_id",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Verification workspace aggregate"
          }
        },
        "summary": "Read the bounded, customer-scoped verification aggregate"
      }
    },
    "/v1/dashboard/webhooks": {
      "get": {
        "description": "Returns project endpoints, recent delivery metadata, and retry policy without webhook secrets.",
        "responses": {
          "200": {
            "description": "Webhook workspace aggregate"
          }
        },
        "summary": "Read the bounded, customer-scoped Webhooks aggregate"
      }
    },
    "/v1/do-not-call": {
      "get": {
        "responses": {
          "200": {
            "description": "Do-not-call entries and pagination"
          }
        },
        "summary": "List the workspace do-not-call register (bounded page)",
        "x-required-scopes": [
          "calls:read"
        ]
      },
      "post": {
        "description": "Every outbound path -- campaigns and the direct calls API -- refuses these numbers from then on. Only the workspace adds numbers here: a caller asking not to be contacted is recorded as the call's outcome, never written to the register automatically.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DoNotCallAdd"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Numbers added and per-row rejections"
          }
        },
        "summary": "Add numbers to the do-not-call register",
        "x-required-scopes": [
          "calls:write"
        ]
      }
    },
    "/v1/do-not-call/{phone_number}": {
      "delete": {
        "parameters": [
          {
            "in": "path",
            "name": "phone_number",
            "required": true,
            "schema": {
              "description": "URL-encoded E.164",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removal count"
          }
        },
        "summary": "Remove one number from the do-not-call register",
        "x-required-scopes": [
          "calls:write"
        ]
      }
    },
    "/v1/end-users": {
      "get": {
        "description": "One bounded page, newest first, as an object: the items under `end_users` and `pagination`. Follow `pagination.next_cursor` while `pagination.has_more` is true; a walk returns every row that exists for the whole walk exactly once.",
        "parameters": [
          {
            "description": "Optional with a workspace key, which lists its own organization. An operator credential must name one: no list spans organizations.",
            "in": "query",
            "name": "organization_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page size, 1 to 200; 50 when omitted. A value outside that range or not a whole number is a 400, never shortened.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The previous page's `pagination.next_cursor`, exactly as received. Anything else is a 400.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "end_users": {
                      "description": "At most `limit` items, at most 200.",
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/ListPagination"
                    }
                  },
                  "required": [
                    "end_users",
                    "pagination"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "One page. Server-Timing and X-Skysay-Query-Count describe the read; Cache-Control is no-store."
          },
          "400": {
            "description": "`limit` outside its range or missing where required, a `cursor` that is not a `next_cursor`, no organization for an operator credential, or a project of another organization."
          }
        },
        "summary": "List end-user verification profiles"
      }
    },
    "/v1/end-users/{end_user_profile_id}/registration-status": {
      "get": {
        "responses": {
          "200": {
            "description": "Registration status"
          }
        },
        "summary": "KYC + carrier identity + per-number registration status with derived customer status, and a headline naming how many number registrations Skysay has submitted to the carrier (empty when none is currently submitted, and also when the profile is rejected or needs more information)"
      }
    },
    "/v1/end-users/{end_user_profile_id}/submit-registration": {
      "post": {
        "responses": {
          "201": {
            "description": "Per-step mutation request ids and per-number registration state"
          }
        },
        "summary": "Enqueue the carrier end-user registration chain (identity/address/proofs/verification) through the operator queue; identity is org-level and reused"
      }
    },
    "/v1/gateway-deployments": {
      "get": {
        "description": "One bounded page, newest first, as an object: the items under `gateway_deployments` and `pagination`. Follow `pagination.next_cursor` while `pagination.has_more` is true; a walk returns every row that exists for the whole walk exactly once.",
        "parameters": [
          {
            "description": "Optional with a workspace key, which lists its own organization. An operator credential must name one: no list spans organizations.",
            "in": "query",
            "name": "organization_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only this project's rows. A project of another organization is a 400.",
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page size, 1 to 200; 50 when omitted. A value outside that range or not a whole number is a 400, never shortened.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The previous page's `pagination.next_cursor`, exactly as received. Anything else is a 400.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "gateway_deployments": {
                      "description": "At most `limit` items, at most 200.",
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/ListPagination"
                    }
                  },
                  "required": [
                    "gateway_deployments",
                    "pagination"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "One page. Server-Timing and X-Skysay-Query-Count describe the read; Cache-Control is no-store."
          },
          "400": {
            "description": "`limit` outside its range or missing where required, a `cursor` that is not a `next_cursor`, no organization for an operator credential, or a project of another organization."
          }
        },
        "summary": "List media gateways"
      },
      "post": {
        "description": "Registers a gateway deployment. organization_id and project_id are optional: a self-hoster holding one API key has no route to look either id up, so they default to the principal's own organization and that organization's first project. A supplied organization must be the principal's own, and a supplied project must belong to it.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GatewayDeployment"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Gateway"
          }
        },
        "summary": "Create a deployment"
      }
    },
    "/v1/gateway-deployments/{gateway_id}/call-lifecycle-events": {
      "post": {
        "responses": {
          "200": {
            "description": "Idempotent replay"
          },
          "201": {
            "description": "Lifecycle event recorded"
          }
        },
        "summary": "Record idempotent authoritative SIP/room lifecycle for call state and billing"
      }
    },
    "/v1/gateway-deployments/{gateway_id}/health-report": {
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GatewayHealthReport"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Updated gateway deployment"
          }
        },
        "summary": "Report self-hosted gateway health, version, storage, and last test result"
      }
    },
    "/v1/gateway-deployments/{gateway_id}/outbound-calls": {
      "post": {
        "description": "For a self-hosted local-mode deployment, runs outbound admission, wallet reservation, call creation and bounded execution-slot allocation, then returns a single-use tca_ token and dial plan instead of enqueueing a job. Tenant/project authority comes from the deployment row; any supplied identity must agree. Caller ID must be an active number of that project. Gateway tokens require both calls:authorize and gateways:report; missing scopes and a disabled live gate refuse before reservation. Denied authorization returns no token; an idempotency replay returns the original call without a new token. Self-hosted queue-mode workers remain organization-bound. The separately operated platform_hosted mode is internal: it only authorizes an existing hosted call under its current worker lease, deriving tenant/configuration from that call, never creating a caller-selected tenant call. Platform mode is staged behind its rollout gate and is not available through customer deployment creation.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GatewayOutboundCall"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Admission result: the call, and the dial plan plus token when authorized"
          }
        },
        "summary": "Admit an outbound call a local-mode deployment will dial itself",
        "x-required-scopes": [
          "calls:authorize",
          "gateways:report"
        ]
      }
    },
    "/v1/gateway-deployments/{gateway_id}/outbound-execution-mode": {
      "patch": {
        "description": "Sets outbound_execution_mode to 'queue' (the default: admission enqueues a call.outbound_originate job an organization-scoped worker claims) or 'local' (the deployment asks for admission and dials itself, so no organization-scoped credential is needed on that box). Only a self-hosted deployment may be 'local'. The switch is refused in either direction while calls are still in flight -- going local while outbound jobs are still queued would strand them, and going back to queue while the deployment is still dialing locally would abandon those calls. Once the organization has a local-mode deployment and no queue-mode deployment left, a remote POST /v1/calls for its numbers fails closed rather than enqueueing a job nothing would claim.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GatewayOutboundExecutionMode"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Updated gateway deployment"
          }
        },
        "summary": "Choose whether this deployment dials its own outbound calls",
        "x-required-scopes": [
          "gateways:write"
        ]
      }
    },
    "/v1/gateway-deployments/{gateway_id}/tokens": {
      "get": {
        "responses": {
          "200": {
            "description": "Gateway tokens and their page marker"
          }
        },
        "summary": "List a gateway's machine tokens, masked to their prefixes"
      }
    },
    "/v1/gateway-deployments/{gateway_id}/tokens/{gateway_token_id}/revoke": {
      "post": {
        "responses": {
          "200": {
            "description": "Revoked gateway token"
          }
        },
        "summary": "Revoke one gateway machine token immediately"
      }
    },
    "/v1/health": {
      "get": {
        "responses": {
          "200": {
            "description": "Liveness report"
          }
        },
        "summary": "Read shallow public API liveness"
      }
    },
    "/v1/hosted-ai-credentials": {
      "get": {
        "description": "Returns credential metadata only. Plaintext, ciphertext, nonce, encryption-key identifiers, and secret fingerprints are never returned.",
        "parameters": [
          {
            "in": "query",
            "name": "project_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "maximum": 100000,
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bounded credential metadata and pagination"
          }
        },
        "summary": "List bounded metadata for hosted AI BYOK credentials",
        "x-required-scopes": [
          "agents:read"
        ]
      },
      "post": {
        "description": "Stores an encrypted credential in pending_validation status. The secret is accepted only in this request and is never returned. A selected BYOK component never falls back to platform credentials.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "component": {
                    "enum": [
                      "stt",
                      "llm",
                      "tts"
                    ],
                    "type": "string"
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "project_id": {
                    "type": "string"
                  },
                  "provider": {
                    "type": "string"
                  },
                  "secret": {
                    "type": "string",
                    "writeOnly": true
                  }
                },
                "required": [
                  "organization_id",
                  "project_id",
                  "component",
                  "provider",
                  "secret"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Credential metadata in pending validation"
          }
        },
        "summary": "Store a write-only hosted AI provider credential",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/hosted-ai-credentials/{credential_id}/revoke": {
      "post": {
        "description": "Revoked credentials cannot be selected or leased for a call.",
        "parameters": [
          {
            "in": "path",
            "name": "credential_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "organization_id": {
                    "type": "string"
                  },
                  "project_id": {
                    "type": "string"
                  }
                },
                "required": [
                  "organization_id",
                  "project_id"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Revoked credential metadata"
          }
        },
        "summary": "Revoke a hosted AI credential",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/hosted-ai-credentials/{credential_id}/validate": {
      "post": {
        "description": "Only definitive credential rejection becomes invalid. Transient provider/network failure remains pending and cannot be selected for a call.",
        "parameters": [
          {
            "in": "path",
            "name": "credential_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "organization_id": {
                    "type": "string"
                  },
                  "project_id": {
                    "type": "string"
                  }
                },
                "required": [
                  "organization_id",
                  "project_id"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Credential metadata with validation outcome"
          }
        },
        "summary": "Run a non-billable validation probe for a hosted AI credential",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/integrations": {
      "get": {
        "description": "Endpoint signing secrets are write-only and never returned.",
        "parameters": [
          {
            "in": "query",
            "name": "project_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "maximum": 100000,
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bounded Integration metadata and pagination"
          }
        },
        "summary": "List bounded metadata for reusable customer Integrations",
        "x-required-scopes": [
          "agents:read"
        ]
      },
      "post": {
        "description": "The signing secret is accepted only in this request and is never returned. The required initial_action makes the setup atomic; no saved Integration exists without an allowed action.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "endpoint_url": {
                    "format": "uri",
                    "type": "string"
                  },
                  "initial_action": {
                    "description": "A read-only action with name, input_schema, output_schema, description, and timeout_seconds.",
                    "type": "object"
                  },
                  "name": {
                    "type": "string"
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "project_id": {
                    "type": "string"
                  },
                  "signing_secret": {
                    "type": "string",
                    "writeOnly": true
                  }
                },
                "required": [
                  "organization_id",
                  "project_id",
                  "name",
                  "endpoint_url",
                  "signing_secret",
                  "initial_action"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Safe Integration and initial action metadata"
          }
        },
        "summary": "Create one reusable encrypted Integration and its first read action",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/integrations/{integration_id}/actions": {
      "get": {
        "parameters": [
          {
            "in": "path",
            "name": "integration_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "project_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "maximum": 100000,
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bounded Integration action metadata and pagination"
          }
        },
        "summary": "List typed actions on one reusable Integration",
        "x-required-scopes": [
          "agents:read"
        ]
      },
      "post": {
        "description": "V1 accepts read-only actions only. Mutation actions remain unavailable until Skysay provides a server-owned caller-confirmation flow.",
        "parameters": [
          {
            "in": "path",
            "name": "integration_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "input_schema": {
                    "type": "object"
                  },
                  "name": {
                    "type": "string"
                  },
                  "operation_kind": {
                    "enum": [
                      "read"
                    ],
                    "type": "string"
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "output_schema": {
                    "type": "object"
                  },
                  "project_id": {
                    "type": "string"
                  },
                  "requires_confirmation": {
                    "enum": [
                      false
                    ],
                    "type": "boolean"
                  },
                  "timeout_seconds": {
                    "maximum": 10,
                    "minimum": 1,
                    "type": "integer"
                  }
                },
                "required": [
                  "organization_id",
                  "project_id",
                  "name",
                  "input_schema",
                  "output_schema"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Integration action metadata"
          }
        },
        "summary": "Add an allowlisted typed read action to an Integration",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/integrations/{integration_id}/disable": {
      "post": {
        "parameters": [
          {
            "in": "path",
            "name": "integration_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "organization_id": {
                    "type": "string"
                  },
                  "project_id": {
                    "type": "string"
                  }
                },
                "required": [
                  "organization_id",
                  "project_id"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Disabled Integration metadata"
          }
        },
        "summary": "Disable an Integration and remove its actions from the active worker allowlist",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/knowledge-bases": {
      "get": {
        "responses": {
          "200": {
            "description": "Knowledge bases and pagination"
          }
        },
        "summary": "List bounded active knowledge bases for the authenticated project"
      },
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KnowledgeBase"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Knowledge base"
          }
        },
        "summary": "Create a private knowledge base in the authenticated workspace"
      }
    },
    "/v1/knowledge-bases/{knowledge_base_id}": {
      "delete": {
        "responses": {
          "202": {
            "description": "Deletion queued"
          }
        },
        "summary": "Hide and asynchronously delete one knowledge base and its private sources"
      },
      "get": {
        "responses": {
          "200": {
            "description": "Knowledge base"
          }
        },
        "summary": "Read one knowledge base"
      },
      "patch": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KnowledgeBase"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Knowledge base"
          }
        },
        "summary": "Rename one knowledge base"
      }
    },
    "/v1/knowledge-bases/{knowledge_base_id}/documents": {
      "get": {
        "responses": {
          "200": {
            "description": "Knowledge documents and pagination"
          }
        },
        "summary": "List bounded document lifecycle state for one knowledge base"
      },
      "post": {
        "description": "Send raw bytes with X-Skysay-Filename and tenant-bound organization_id/project_id query parameters. The API queues ingestion; it does not parse or embed in the request.",
        "requestBody": {
          "content": {
            "application/octet-stream": {
              "schema": {
                "format": "binary",
                "type": "string"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Document queued for ingestion"
          }
        },
        "summary": "Direct-upload one private TXT, Markdown, PDF, or DOCX source"
      }
    },
    "/v1/knowledge-bases/{knowledge_base_id}/test": {
      "post": {
        "description": "Customer-initiated only. The route binds the bearer organization/project and path base, reuses production passage retrieval, then asks Gemini for a short grounded preview. It returns exact visible source excerpts with an answer, explicit not_found when evidence is absent, or unavailable; it never creates a call, billing entry, retrieval event, action, or model-selectable scope.",
        "parameters": [
          {
            "in": "path",
            "name": "knowledge_base_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KnowledgeBaseTest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Grounded answer and source excerpts, explicit not_found, or safe unavailable outcome"
          }
        },
        "summary": "Test one grounded answer against a private knowledge base",
        "x-required-scopes": [
          "knowledge:read"
        ]
      }
    },
    "/v1/knowledge-documents/{document_id}": {
      "delete": {
        "responses": {
          "202": {
            "description": "Deletion queued"
          }
        },
        "summary": "Hide and asynchronously delete one private source document"
      }
    },
    "/v1/knowledge-documents/{document_id}/replace": {
      "post": {
        "description": "Send raw replacement bytes with X-Skysay-Filename and tenant-bound organization_id/project_id query parameters. The replacement is stored and queued before the unreadable source is scheduled for deletion; this endpoint is unavailable for searchable or retryable documents.",
        "parameters": [
          {
            "in": "path",
            "name": "document_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "organization_id",
            "required": false,
            "schema": {
              "description": "Optional compatibility echo; the bearer workspace is authoritative.",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "project_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/octet-stream": {
              "schema": {
                "format": "binary",
                "type": "string"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Replacement queued and unreadable source scheduled for deletion"
          }
        },
        "summary": "Replace one permanently unreadable private source document",
        "x-required-scopes": [
          "knowledge:write"
        ]
      }
    },
    "/v1/knowledge-documents/{document_id}/retry": {
      "post": {
        "description": "Queues a fresh generation only when the document is in a safe retryable state or its ready row is missing/has lost its derived normalized-text artifact. It does not retry a healthy searchable document and it never exposes parser, provider, or storage exception text.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KnowledgeDocumentCommand"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "description": "Retry queued"
          }
        },
        "summary": "Retry a recoverable knowledge document ingestion"
      }
    },
    "/v1/knowledge-documents/{document_id}/text": {
      "get": {
        "description": "Reads only the tenant-bound private derived text artifact, verifies its recorded hash/count, and never returns the original object, a storage key, or a download URL. The response is bounded to 50,000 characters with offset pagination and performs no model/provider generation or mutation.",
        "parameters": [
          {
            "in": "path",
            "name": "document_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "organization_id",
            "required": false,
            "schema": {
              "description": "Optional compatibility echo; the bearer workspace is authoritative.",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "project_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "maximum": 50000,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "maximum": 1000000,
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Normalized text page and explicit pagination metadata"
          }
        },
        "summary": "Read a bounded page of normalized extracted text for one ready document",
        "x-required-scopes": [
          "knowledge:read"
        ]
      }
    },
    "/v1/kyc/sessions": {
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "end_user_profile_id": {
                    "type": "string"
                  },
                  "expected_details": {
                    "type": "object"
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "profile_type": {
                    "enum": [
                      "individual",
                      "business"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "organization_id",
                  "profile_type"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Verification session with hosted didit URL; reused when one is already pending or approved"
          },
          "503": {
            "description": "error kyc_provider_not_configured: identity verification is not available on this deployment for the profile type. Retry later or contact support."
          }
        },
        "summary": "Start a didit.me KYC (individual) or KYB (business) verification session for an end-user profile"
      }
    },
    "/v1/kyc/sessions/{end_user_profile_id}": {
      "get": {
        "responses": {
          "200": {
            "description": "KYC session state"
          }
        },
        "summary": "Read KYC/KYB status and the extracted decision summary (no raw document URLs)"
      }
    },
    "/v1/messages": {
      "get": {
        "description": "One bounded page, newest first by `created_at`, ties by creation order, as an object: the items under `messages` and `pagination`. Follow `pagination.next_cursor` while `pagination.has_more` is true; a walk returns every row that exists for the whole walk exactly once.",
        "parameters": [
          {
            "description": "Optional with a workspace key, which lists its own organization. An operator credential must name one: no list spans organizations.",
            "in": "query",
            "name": "organization_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only this project's rows. A project of another organization is a 400.",
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page size, 1 to 500; 100 when omitted. A value outside that range or not a whole number is a 400, never shortened.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 100,
              "maximum": 500,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The previous page's `pagination.next_cursor`, exactly as received. Anything else is a 400.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only this conversation's messages: its history, newest first. A conversation of another workspace or project is a 404.",
            "in": "query",
            "name": "conversation_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "messages": {
                      "description": "At most `limit` items, at most 500.",
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/ListPagination"
                    }
                  },
                  "required": [
                    "messages",
                    "pagination"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "One page. Server-Timing and X-Skysay-Query-Count describe the read; Cache-Control is no-store."
          },
          "400": {
            "description": "`limit` outside its range or missing where required, a `cursor` that is not a `next_cursor`, no organization for an operator credential, or a project of another organization."
          },
          "404": {
            "description": "`conversation_id` names no conversation of this workspace and project."
          }
        },
        "summary": "List SMS messages"
      },
      "post": {
        "description": "Requires sms:send. Message body, provider metadata, error detail and idempotency key are returned only with sms:read, for both fresh sends and idempotent replays.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SmsMessage"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Message"
          }
        },
        "summary": "Send a policy-gated outbound SMS"
      }
    },
    "/v1/messages/inbound/didww": {
      "get": {
        "responses": {
          "201": {
            "description": "Message"
          }
        },
        "summary": "Receive the carrier inbound SMS HTTP callback through query variables"
      },
      "post": {
        "responses": {
          "201": {
            "description": "Message"
          }
        },
        "summary": "Receive the carrier inbound SMS HTTP callback"
      },
      "put": {
        "responses": {
          "201": {
            "description": "Message"
          }
        },
        "summary": "Receive the carrier inbound SMS HTTP callback"
      }
    },
    "/v1/messages/status/didww": {
      "get": {
        "responses": {
          "200": {
            "description": "Message"
          }
        },
        "summary": "Receive the carrier SMS delivery callback through query variables"
      },
      "post": {
        "responses": {
          "200": {
            "description": "Message"
          }
        },
        "summary": "Receive the carrier SMS delivery callback"
      },
      "put": {
        "responses": {
          "200": {
            "description": "Message"
          }
        },
        "summary": "Receive the carrier SMS delivery callback"
      }
    },
    "/v1/messages/{message_id}/delivery-events": {
      "get": {
        "responses": {
          "200": {
            "description": "Delivery events"
          }
        },
        "summary": "List SMS provider delivery events for one message"
      }
    },
    "/v1/messages/{message_id}/translations": {
      "get": {
        "description": "Pure read: target selection, original/translation/both display, and polling never invoke a provider or create paid work. Artifacts are fenced to the current source revision. Single-source reads can calculate a bounded price estimate for retained older text without saving it or creating work.",
        "parameters": [
          {
            "in": "path",
            "name": "message_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "target_language",
            "schema": {
              "enum": [
                "ar",
                "bn",
                "bg",
                "ca",
                "zh",
                "hr",
                "cs",
                "da",
                "nl",
                "en",
                "et",
                "fi",
                "fr",
                "de",
                "el",
                "gu",
                "he",
                "hi",
                "hu",
                "id",
                "it",
                "ja",
                "kn",
                "ko",
                "lv",
                "lt",
                "ms",
                "ml",
                "mr",
                "no",
                "fa",
                "pl",
                "pt",
                "ro",
                "ru",
                "sr",
                "si",
                "sk",
                "sl",
                "es",
                "sw",
                "sv",
                "ta",
                "te",
                "th",
                "tr",
                "uk",
                "ur",
                "vi"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommunicationTranslation"
                }
              }
            },
            "description": "Canonical cached translation state. Originals remain readable on every failure."
          },
          "400": {
            "description": "Invalid target, limit, or cursor."
          },
          "404": {
            "description": "Source absent from the credential's own workspace, including for operator credentials."
          },
          "409": {
            "description": "Source changed; restart from the first page."
          }
        },
        "summary": "Read cached message translations",
        "x-required-scopes": [
          "sms:read"
        ]
      },
      "post": {
        "description": "Explicitly request translation for any accessible settled source with retained usable text, regardless of its creation date, even when automatic agent translation is disabled. This enrolls only the requested source for revision recovery and never enables automatic translation of other historical sources. $0.02 per 1,000 original words needing translation per target, minimum $0.01 per successfully translated source/target. Matching-language-only text and cached reads cost zero. Failed attempts and platform corrections never double-charge. No funds leaves translation waiting_funds and does not resume calls or SMS.",
        "parameters": [
          {
            "in": "path",
            "name": "message_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TranslationRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CommunicationTranslation"
                }
              }
            },
            "description": "Canonical cached translation state. Originals remain readable on every failure."
          },
          "400": {
            "description": "Invalid target or ineligible source."
          },
          "403": {
            "description": "Missing content/paid-write scope, suspended source or workspace."
          },
          "404": {
            "description": "Source not in the credential's workspace."
          }
        },
        "summary": "Request paid message translation",
        "x-required-scopes": [
          "sms:read",
          "translations:write"
        ]
      }
    },
    "/v1/number-catalog": {
      "get": {
        "responses": {
          "200": {
            "description": "Number catalog; each row and market summary carries orderable (true|false|null) and not_orderable_reason (row: number_type_not_priced or sku_bundles_channels; market summary: number_type_not_priced or carrier_bundles_channels)"
          }
        },
        "summary": "Search the stored number catalog with customer setup/monthly prices, capabilities, and onboarding terms"
      }
    },
    "/v1/number-requests": {
      "get": {
        "description": "One bounded page, newest first, as an object: the items under `number_requests` and `pagination`. Follow `pagination.next_cursor` while `pagination.has_more` is true; a walk returns every row that exists for the whole walk exactly once.",
        "parameters": [
          {
            "description": "Optional with a workspace key, which lists its own organization. An operator credential must name one: no list spans organizations.",
            "in": "query",
            "name": "organization_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only this project's rows. A project of another organization is a 400.",
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page size, 1 to 100; 50 when omitted. A value outside that range or not a whole number is a 400, never shortened.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The previous page's `pagination.next_cursor`, exactly as received. Anything else is a 400.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only requests in this status. The filter applies on every page of a walk.",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "number_requests": {
                      "description": "At most `limit` items, at most 100.",
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/ListPagination"
                    }
                  },
                  "required": [
                    "number_requests",
                    "pagination"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "One page. Server-Timing and X-Skysay-Query-Count describe the read; Cache-Control is no-store."
          },
          "400": {
            "description": "`limit` outside its range or missing where required, a `cursor` that is not a `next_cursor`, no organization for an operator credential, or a project of another organization."
          }
        },
        "summary": "List managed phone-number requests"
      },
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ManagedNumberRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Number request"
          },
          "409": {
            "description": "error inbound_coverage_missing: a confirmed purchase in a market Skysay cannot yet carry incoming calls for (blocker capacity_plan_unavailable or carrier_rate_missing). Refused before the request is created; nothing is charged."
          }
        },
        "summary": "Request a managed Skysay phone number"
      }
    },
    "/v1/number-requests/{request_id}": {
      "get": {
        "responses": {
          "200": {
            "description": "Number request"
          }
        },
        "summary": "Get one managed phone-number request"
      },
      "patch": {
        "responses": {
          "200": {
            "description": "Number request"
          }
        },
        "summary": "Update managed phone-number request status or notes"
      }
    },
    "/v1/numbers": {
      "get": {
        "description": "One bounded page, newest first, as an object: the items under `numbers` and `pagination`. Follow `pagination.next_cursor` while `pagination.has_more` is true; a walk returns every row that exists for the whole walk exactly once.",
        "parameters": [
          {
            "description": "Optional with a workspace key, which lists its own organization. An operator credential must name one: no list spans organizations.",
            "in": "query",
            "name": "organization_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only this project's rows. A project of another organization is a 400.",
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page size, 1 to 100; 50 when omitted. A value outside that range or not a whole number is a 400, never shortened.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The previous page's `pagination.next_cursor`, exactly as received. Anything else is a 400.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "numbers": {
                      "description": "At most `limit` items, at most 100.",
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/ListPagination"
                    }
                  },
                  "required": [
                    "numbers",
                    "pagination"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "One page. Server-Timing and X-Skysay-Query-Count describe the read; Cache-Control is no-store."
          },
          "400": {
            "description": "`limit` outside its range or missing where required, a `cursor` that is not a `next_cursor`, no organization for an operator credential, or a project of another organization."
          }
        },
        "summary": "List phone numbers"
      },
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PhoneNumberRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Phone number"
          },
          "400": {
            "description": "organization_id or project_id is missing or blank; an unsupported status; or an agent_id or end_user_id that is not one of this workspace's. An id that does not exist and an id owned by another workspace answer identically, so neither enumerates the other's ids"
          }
        },
        "summary": "Operator/internal attach an owned carrier number or create a local fake number"
      }
    },
    "/v1/numbers/requirements": {
      "get": {
        "responses": {
          "200": {
            "description": "registration_required, carrier_proof_requirement (none|required|unknown), orderable (true|false|null) with not_orderable_reason (number_type_not_priced|carrier_bundles_channels), field/document lists, carrier_forms (the carrier forms this profile type must download, sign and upload: id, name, url, format, notes with the carrier's own qualifiers such as an age limit; document_names are their names), activation estimate, conflict_note"
          }
        },
        "summary": "Resolve honest activation requirements for a country/number-type/profile-type (override > live did_groups flag > dataset prefix flag; requirement text only documents fields)"
      }
    },
    "/v1/numbers/{number_id}/activation": {
      "get": {
        "responses": {
          "200": {
            "description": "Activation state"
          }
        },
        "summary": "Customer-facing number activation state: instant | pending_verification | approved | needs_info | rejected with blocking items"
      }
    },
    "/v1/numbers/{number_id}/detach-agent": {
      "post": {
        "responses": {
          "200": {
            "description": "Number rules after the detach"
          }
        },
        "summary": "Compatibility alias for detaching a phone number from its agent",
        "x-required-scopes": [
          "numbers:write"
        ]
      }
    },
    "/v1/numbers/{number_id}/number-rules": {
      "get": {
        "responses": {
          "200": {
            "description": "Number rules"
          }
        },
        "summary": "Compatibility alias for phone-number Number rules"
      },
      "patch": {
        "responses": {
          "200": {
            "description": "Number rules"
          }
        },
        "summary": "Compatibility alias for updating phone-number Number rules"
      }
    },
    "/v1/organizations": {
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateOrganization"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Workspace created"
          }
        },
        "summary": "Create a sandbox customer workspace when signup mode allows it"
      }
    },
    "/v1/organizations/{organization_id}/capabilities": {
      "get": {
        "description": "Pure read. Every workspace has exactly one record from creation on: `capabilities.status` is `not_activated` until the workspace clears verification (an operator's business review, or its first registration-free number), when it becomes `active` on the default profile, the channels the workspace holds and every destination except the sanctioned set (`all_countries: true`). `pending_review` means a request is with an Skysay operator and nothing new is authorized yet; `suspended` means an operator paused outbound. When `all_countries` is false, `countries` is the explicit allowlist (ISO 3166-1 alpha-2). `calling_rules` is the application cadence contract an operator recorded on approval, or null, and `calling_rules_active` says whether it is enforced right now. Every outbound call and SMS is authorized against this record (see POST /v1/policies/authorize-outbound).",
        "parameters": [
          {
            "in": "path",
            "name": "organization_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CapabilitiesDetail"
                }
              }
            },
            "description": "Capabilities record, calling rules, available profiles and usage"
          },
          "403": {
            "description": "The credential does not belong to this organization"
          },
          "404": {
            "description": "Unknown organization"
          }
        },
        "summary": "Read the workspace's capabilities record, the selectable use-case profiles, and live usage against its caps",
        "x-required-scopes": [
          "onboarding:read"
        ]
      },
      "post": {
        "description": "Select the use-case profiles, channels and destinations outbound should be authorized for, and accept the outbound policy by sending `policy_version`. The destination choice is explicit: `all_countries: true` (every destination except the sanctioned set) or `countries` naming at least one well-formed ISO 3166-1 alpha-2 code. A request with neither, with an empty list, or with an entry that is not a two-letter code is refused with 400 and nothing is written, so an active allowlist is never widened or emptied by a request that was refused. A verified workspace that accepts the policy and selects no custom profile is activated immediately within the profiles' default caps; anything else opens an Skysay review, and what is already active stays active until the review resolves. `calling_rules` cannot be set here; an operator records them on approval.",
        "parameters": [
          {
            "in": "path",
            "name": "organization_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CapabilitiesRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Capabilities"
                }
              }
            },
            "description": "The stored record, active or pending review"
          },
          "400": {
            "description": "No valid profile selected, no destination chosen (all_countries or a non-empty countries list), an unrecognized country code, or calling_rules / the retired metadata.operational_profile supplied"
          },
          "403": {
            "description": "The credential does not belong to this organization"
          },
          "404": {
            "description": "Unknown organization"
          }
        },
        "summary": "Request or change the workspace's capabilities",
        "x-required-scopes": [
          "onboarding:write"
        ]
      }
    },
    "/v1/organizations/{organization_id}/capabilities/accept-policy": {
      "post": {
        "description": "Stores `policy_version` with the acceptance time and actor and leaves the profiles, channels, destinations, limits and status exactly as they are: it never activates, suspends or queues anything by itself. Use POST /v1/organizations/{organization_id}/capabilities to change what the workspace is authorized for.",
        "parameters": [
          {
            "in": "path",
            "name": "organization_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CapabilitiesPolicyAcceptance"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Capabilities"
                }
              }
            },
            "description": "The stored record"
          },
          "400": {
            "description": "No policy_version supplied and none already stored"
          },
          "403": {
            "description": "The credential does not belong to this organization"
          }
        },
        "summary": "Record acceptance of the outbound policy without changing the capabilities selection",
        "x-required-scopes": [
          "onboarding:write"
        ]
      }
    },
    "/v1/organizations/{organization_id}/capabilities/destination-restrictions": {
      "post": {
        "description": "Active workspaces only. Store customer narrowing independently of operator-granted capabilities. countries=null removes only this extra restriction; a non-empty array narrows calls and SMS to those available countries. No activation, spending, caller-ID or operator permission changes. revision must match destination_revision from GET capabilities; stale edits fail without writes. Runtime always intersects with current workspace, platform and number rules.",
        "parameters": [
          {
            "in": "path",
            "name": "organization_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "countries": {
                    "items": {
                      "pattern": "^[A-Z]{2}$",
                      "type": "string"
                    },
                    "maxItems": 300,
                    "minItems": 1,
                    "nullable": true,
                    "type": "array"
                  },
                  "revision": {
                    "minimum": 0,
                    "type": "integer"
                  }
                },
                "required": [
                  "countries",
                  "revision"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Saved countries and incremented revision"
          },
          "400": {
            "description": "Invalid, stale or inactive selection; no changes"
          },
          "403": {
            "description": "Workspace access denied"
          }
        },
        "summary": "Set optional workspace destination restrictions",
        "x-required-scopes": [
          "onboarding:write"
        ]
      }
    },
    "/v1/organizations/{organization_id}/capabilities/limit-requests": {
      "post": {
        "parameters": [
          {
            "in": "path",
            "name": "organization_id",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "reason": {
                    "maxLength": 2000,
                    "minLength": 10,
                    "type": "string"
                  }
                },
                "required": [
                  "reason"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Review id and status"
          },
          "400": {
            "description": "Inactive workspace or invalid reason"
          },
          "403": {
            "description": "Workspace access denied"
          }
        },
        "summary": "Request higher limits without changing current permissions or buying a plan",
        "x-required-scopes": [
          "onboarding:write"
        ]
      }
    },
    "/v1/phone-numbers/{number_id}/detach-agent": {
      "post": {
        "description": "A pure control-plane unbind: the number stays imported and owned by the workspace, its Number rules become disabled, and no carrier or provider operation happens. Inbound calls are then declined until an agent is attached again or the rules are changed. A number whose rules already answer without an agent replays as a no-op with idempotent: true. Optionally name the binding your confirmation showed with expected_agent_id, or expected_agent_endpoint_id for a rule that answers through an endpoint alone; if the number meanwhile answers with a different one the command refuses with 409 instead of detaching it.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "expected_agent_endpoint_id": {
                    "description": "The agent endpoint you expect to be answering, for a rule that answers through an endpoint whose owning agent you cannot name. Checked the same way as expected_agent_id; send either, both, or neither.",
                    "type": "string"
                  },
                  "expected_agent_id": {
                    "description": "The agent you expect to be answering. The command refuses with 409 when a different agent answers by the time it runs, or when the rule no longer names an agent at all; a binding that is already gone entirely still replays as the idempotent no-op.",
                    "type": "string"
                  }
                },
                "type": "object"
              }
            }
          },
          "required": false
        },
        "responses": {
          "200": {
            "description": "Number rules after the detach"
          },
          "409": {
            "description": "The number now answers with a binding other than the one named; nothing was detached"
          }
        },
        "summary": "Detach a phone number from the agent that answers it",
        "x-required-scopes": [
          "numbers:write"
        ]
      }
    },
    "/v1/phone-numbers/{number_id}/number-rules": {
      "get": {
        "responses": {
          "200": {
            "description": "Number rules"
          }
        },
        "summary": "Get customer-facing Number rules for a phone number"
      },
      "patch": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NumberRules"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Number rules"
          }
        },
        "summary": "Update customer-facing Number rules for a phone number"
      }
    },
    "/v1/phone-servers": {
      "get": {
        "description": "One bounded page, newest first, as an object: the items under `phone_servers` and `pagination`. Follow `pagination.next_cursor` while `pagination.has_more` is true; a walk returns every row that exists for the whole walk exactly once. A project page includes the organization's servers that belong to no project.",
        "parameters": [
          {
            "description": "Optional with a workspace key, which lists its own organization. An operator credential must name one: no list spans organizations.",
            "in": "query",
            "name": "organization_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only this project's rows. A project of another organization is a 400.",
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page size, 1 to 200; 50 when omitted. A value outside that range or not a whole number is a 400, never shortened.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The previous page's `pagination.next_cursor`, exactly as received. Anything else is a 400.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "pagination": {
                      "$ref": "#/components/schemas/ListPagination"
                    },
                    "phone_servers": {
                      "description": "At most `limit` items, at most 200.",
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    }
                  },
                  "required": [
                    "phone_servers",
                    "pagination"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "One page. Server-Timing and X-Skysay-Query-Count describe the read; Cache-Control is no-store."
          },
          "400": {
            "description": "`limit` outside its range or missing where required, a `cursor` that is not a `next_cursor`, no organization for an operator credential, or a project of another organization."
          }
        },
        "summary": "List customer phone servers"
      },
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PhoneServer"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Phone server"
          }
        },
        "summary": "Create a customer phone server for self-hosted call media"
      }
    },
    "/v1/phone-servers/{phone_server_id}": {
      "get": {
        "responses": {
          "200": {
            "description": "Phone server"
          }
        },
        "summary": "Get one customer phone server"
      },
      "patch": {
        "responses": {
          "200": {
            "description": "Phone server"
          }
        },
        "summary": "Update one customer phone server"
      }
    },
    "/v1/phone-servers/{phone_server_id}/test": {
      "post": {
        "responses": {
          "200": {
            "description": "Verified phone server"
          }
        },
        "summary": "Dry-run verify a customer phone server before assigning Number rules"
      }
    },
    "/v1/policies/authorize-outbound": {
      "post": {
        "description": "Answers whether POST /v1/calls (or an SMS send) would be refused before dialling, without creating anything. For `channel` `outbound_voice` (the default) it asks both pre-dial questions a call meets, in the same order: first the workspace capabilities (destination country, channel, use-case profile, review signals, caps), then, only if those allow it, the per-call outbound policy (`purpose`, `user_authorization` with both `authorized_by` and `reason`, caller-number readiness, the destination format). `stage` says which question answered: `outbound_authorization` or `outbound_policy`. For any other channel (`sms`) only the capabilities question is asked. This is a preflight of those two gates, not a full admission: number rules, plan concurrency, maintenance windows and the wallet reservation are not evaluated; spend caps are checked only against the `amount_minor` you send, calling rules only when you send `project_id`, and caller-number readiness only when you send `from_number_id` (the call itself always needs one).",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "amount_minor": {
                    "description": "Estimated cost to check spend caps against; 0 skips them.",
                    "minimum": 0,
                    "type": "integer"
                  },
                  "bulk": {
                    "type": "boolean"
                  },
                  "campaign_id": {
                    "type": "string"
                  },
                  "categories": {
                    "items": {
                      "type": "string"
                    },
                    "type": "array"
                  },
                  "channel": {
                    "default": "outbound_voice",
                    "description": "`outbound_voice` (the default) or `sms`; any value other than `outbound_voice` is judged as SMS.",
                    "type": "string"
                  },
                  "from_number_id": {
                    "description": "The caller number; it must belong to the organization (and to `project_id` when sent).",
                    "type": "string"
                  },
                  "metadata": {
                    "description": "Alternatively, every field above except organization_id, channel, to, from_number_id and amount_minor, nested here.",
                    "type": "object"
                  },
                  "organization_id": {
                    "type": "string"
                  },
                  "policy": {
                    "description": "The same `policy` object POST /v1/calls takes.",
                    "type": "object"
                  },
                  "project_id": {
                    "type": "string"
                  },
                  "purpose": {
                    "type": "string"
                  },
                  "recipient_type": {
                    "type": "string"
                  },
                  "relationship": {
                    "type": "string"
                  },
                  "to": {
                    "description": "Destination in E.164. `phone_number` is accepted as the same field.",
                    "type": "string"
                  },
                  "user_authorization": {
                    "description": "Who approved the call and why. Both fields are required on every outbound call.",
                    "properties": {
                      "authorized_by": {
                        "type": "string"
                      },
                      "reason": {
                        "type": "string"
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "organization_id"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "capabilities_snapshot": {
                      "type": "object"
                    },
                    "customer_reason": {
                      "type": "string"
                    },
                    "decision": {
                      "enum": [
                        "allowed",
                        "blocked",
                        "needs_review"
                      ],
                      "type": "string"
                    },
                    "escalation_signals": {
                      "description": "Every reason code the refusing gate gave.",
                      "items": {
                        "type": "string"
                      },
                      "type": "array"
                    },
                    "next_action": {
                      "type": "string"
                    },
                    "policy": {
                      "description": "The per-call policy evaluation; present whenever the voice policy question was asked.",
                      "properties": {
                        "allowed": {
                          "type": "boolean"
                        },
                        "reasons": {
                          "items": {
                            "type": "string"
                          },
                          "type": "array"
                        },
                        "required_actions": {
                          "description": "What to add or do. `provide_user_authorization.authorized_by` and `provide_user_authorization.reason` name a missing user_authorization field.",
                          "items": {
                            "type": "string"
                          },
                          "type": "array"
                        },
                        "risk_level": {
                          "type": "string"
                        }
                      },
                      "type": "object"
                    },
                    "reason_code": {
                      "description": "`authorized` when allowed; otherwise the refusing gate's first reason code.",
                      "type": "string"
                    },
                    "stage": {
                      "description": "Which gate gave the decision.",
                      "enum": [
                        "outbound_authorization",
                        "outbound_policy"
                      ],
                      "type": "string"
                    }
                  },
                  "required": [
                    "decision",
                    "reason_code",
                    "customer_reason",
                    "next_action",
                    "escalation_signals",
                    "stage"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "The decision the first refusing gate gave, or `allowed` when both allow."
          },
          "400": {
            "description": "A malformed request, or a from_number_id that belongs to another organization or project."
          },
          "404": {
            "description": "from_number_id does not exist."
          }
        },
        "summary": "Preflight an outbound call or SMS against the pre-dial gates, persisting nothing"
      }
    },
    "/v1/policies/evaluate": {
      "post": {
        "responses": {
          "200": {
            "description": "Decision"
          }
        },
        "summary": "Evaluate outbound policy"
      }
    },
    "/v1/pricing/catalog": {
      "get": {
        "responses": {
          "200": {
            "description": "Catalog"
          }
        },
        "summary": "List pricing plans"
      }
    },
    "/v1/pricing/estimate": {
      "post": {
        "responses": {
          "200": {
            "description": "Estimate"
          }
        },
        "summary": "Estimate monthly platform cost on the public price book (public_launch_pricing_v1)"
      }
    },
    "/v1/regulatory-requirements": {
      "get": {
        "responses": {
          "200": {
            "description": "Requirements"
          }
        },
        "summary": "List supported country and number-type requirements with simple onboarding terms and cache metadata"
      }
    },
    "/v1/self-hosted/install-plan": {
      "get": {
        "responses": {
          "200": {
            "description": "Self-hosted install plan"
          }
        },
        "summary": "Fetch a mode-specific self-hosted or customer-hosted gateway install plan"
      }
    },
    "/v1/trial-calls": {
      "get": {
        "description": "One bounded page, newest first, as an object: the items under `trial_calls` and `pagination`. Follow `pagination.next_cursor` while `pagination.has_more` is true; a walk returns every row that exists for the whole walk exactly once.",
        "parameters": [
          {
            "description": "Optional with a workspace key, which lists its own organization. An operator credential must name one: no list spans organizations.",
            "in": "query",
            "name": "organization_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only this project's rows. A project of another organization is a 400.",
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page size, 1 to 100; 50 when omitted. A value outside that range or not a whole number is a 400, never shortened.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The previous page's `pagination.next_cursor`, exactly as received. Anything else is a 400.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "pagination": {
                      "$ref": "#/components/schemas/ListPagination"
                    },
                    "trial_calls": {
                      "description": "At most `limit` items, at most 100.",
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    }
                  },
                  "required": [
                    "trial_calls",
                    "pagination"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "One page. Server-Timing and X-Skysay-Query-Count describe the read; Cache-Control is no-store."
          },
          "400": {
            "description": "`limit` outside its range or missing where required, a `cursor` that is not a `next_cursor`, no organization for an operator credential, or a project of another organization."
          }
        },
        "summary": "List historical and offline trial fixtures"
      },
      "post": {
        "description": "Requires synthetic_only=true. New shared-number live PIN trials are retired; use authenticated metered browser sessions or purchased-funded PSTN calls.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TrialCall"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Trial call"
          }
        },
        "summary": "Create an offline synthetic trial fixture"
      }
    },
    "/v1/trial-calls/route": {
      "post": {
        "deprecated": true,
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RouteTrialCall"
              }
            }
          },
          "required": true
        },
        "responses": {
          "400": {
            "description": "Live PIN trials are retired; no call or worker job is created"
          }
        },
        "summary": "Retired shared-number live PIN route"
      }
    },
    "/v1/usage": {
      "get": {
        "responses": {
          "200": {
            "description": "Usage"
          }
        },
        "summary": "Get usage summary"
      }
    },
    "/v1/usage-records": {
      "get": {
        "description": "One bounded page, newest first by `created_at`, ties by creation order, as an object: the items under `usage_records` and `pagination`. Follow `pagination.next_cursor` while `pagination.has_more` is true; a walk returns every row that exists for the whole walk exactly once.",
        "parameters": [
          {
            "description": "Optional with a workspace key, which lists its own organization. An operator credential must name one: no list spans organizations.",
            "in": "query",
            "name": "organization_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only this project's rows. A project of another organization is a 400.",
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page size, 1 to 500; 100 when omitted. A value outside that range or not a whole number is a 400, never shortened.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 100,
              "maximum": 500,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The previous page's `pagination.next_cursor`, exactly as received. Anything else is a 400.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "pagination": {
                      "$ref": "#/components/schemas/ListPagination"
                    },
                    "usage_records": {
                      "description": "At most `limit` items, at most 500.",
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    }
                  },
                  "required": [
                    "usage_records",
                    "pagination"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "One page. Server-Timing and X-Skysay-Query-Count describe the read; Cache-Control is no-store."
          },
          "400": {
            "description": "`limit` outside its range or missing where required, a `cursor` that is not a `next_cursor`, no organization for an operator credential, or a project of another organization."
          }
        },
        "summary": "List usage records"
      }
    },
    "/v1/usage/daily": {
      "get": {
        "responses": {
          "200": {
            "description": "Daily usage"
          }
        },
        "summary": "Get daily usage summary"
      }
    },
    "/v1/usage/monthly": {
      "get": {
        "responses": {
          "200": {
            "description": "Monthly usage"
          }
        },
        "summary": "Get monthly usage summary"
      }
    },
    "/v1/verification/requirement-summaries": {
      "get": {
        "responses": {
          "200": {
            "description": "Compatibility, reusable/pending/missing counts, and missing labels for each requested number type"
          }
        },
        "summary": "Resolve batched per-number-type verification deltas for one country and optional reusable profile"
      }
    },
    "/v1/voice-previews": {
      "post": {
        "description": "An explicit Listen action for one selected TTS voice. It accepts a bounded caller sentence (defaulting to a short product sample only when no audio_environment is supplied), keeps no audio or telemetry, and returns a short in-memory WAV only. A selected outgoing audio_environment requires explicit non-blank text and makes a deterministic, headroom-safe local audition; it never substitutes the text, language, voice, or claims a receiver-side final mix. A small per-workspace fixed-window limit protects provider spend. Every language a provider advertises can be previewed. On a platform credential the voice must be one the workspace could browse and select, except an existing saved hosted agent may re-test its exact current platform-managed selection by supplying agent_id when it has disappeared from a listing; arbitrary typed IDs never create shared runtime evidence. With a selected BYOK credential the credential is validated at the provider and then used to render, so any voice id that provider accepts works, and a platform credential is never a fallback. A GPT-Live-1 voice (provider openai, model gpt-live-1) previews differently: it is a short real live session on the platform credential that speaks Skysay's own sample sentence for the requested language, rendered once per voice, language and sample and then served from a tenant-agnostic cache (the response carries text_source: platform_sample and cached). Typed text is refused with error code voice_preview_fixed_sample rather than replaced, credential_id and delivery do not apply, a platform-wide concurrency cap answers 429 when every preview slot is busy, and the preview is never metered as a call.",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VoicePreviewRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Bounded base64-encoded WAV preview"
          },
          "400": {
            "description": "Invalid tenant-bound project, stack, voice, preview text, audio_environment, or a BYOK credential the provider did not accept; also typed text for a GPT-Live-1 voice (voice_preview_fixed_sample)"
          },
          "404": {
            "description": "The selected BYOK credential does not exist in this workspace"
          },
          "409": {
            "description": "The selected voice cannot be previewed in the requested language"
          },
          "429": {
            "description": "Voice-preview fixed-window limit reached, or every platform-wide GPT-Live preview slot is busy"
          },
          "503": {
            "description": "Managed preview unavailable"
          }
        },
        "summary": "Render one bounded managed voice-selector preview",
        "x-required-scopes": [
          "agents:write"
        ]
      }
    },
    "/v1/wallet": {
      "get": {
        "responses": {
          "200": {
            "description": "Wallet summary"
          }
        },
        "summary": "Read wallet balance and low-balance readiness against recurring and reserved costs"
      }
    },
    "/v1/wallet/ledger": {
      "get": {
        "description": "One bounded page, newest first by `created_at`, ties by creation order, as an object: the items under `entries` and `pagination`. Follow `pagination.next_cursor` while `pagination.has_more` is true; a walk returns every row that exists for the whole walk exactly once. `totals` cover the entries on this page only. For filters by type and date, or a CSV export, use GET /v1/dashboard/billing-history and its /export.",
        "parameters": [
          {
            "description": "Optional with a workspace key, which lists its own organization. An operator credential must name one: no list spans organizations.",
            "in": "query",
            "name": "organization_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page size, 1 to 500; 100 when omitted. A value outside that range or not a whole number is a 400, never shortened.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 100,
              "maximum": 500,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The previous page's `pagination.next_cursor`, exactly as received. Anything else is a 400.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "count": {
                      "description": "Entries on this page.",
                      "minimum": 0,
                      "type": "integer"
                    },
                    "currency": {
                      "type": "string"
                    },
                    "entries": {
                      "description": "At most `limit` items, at most 500.",
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "limit": {
                      "minimum": 1,
                      "type": "integer"
                    },
                    "organization_id": {
                      "type": "string"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/ListPagination"
                    },
                    "totals": {
                      "description": "Credit, debit, reserved and available deltas summed over this page's entries, in minor units (each also as *_cents).",
                      "type": "object"
                    }
                  },
                  "required": [
                    "entries",
                    "pagination",
                    "organization_id",
                    "limit",
                    "count",
                    "currency",
                    "totals"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "One page. Server-Timing and X-Skysay-Query-Count describe the read; Cache-Control is no-store."
          },
          "400": {
            "description": "`limit` outside its range or missing where required, a `cursor` that is not a `next_cursor`, no organization for an operator credential, or a project of another organization."
          }
        },
        "summary": "List the most recent org-level wallet ledger entries and linked usage/call refs"
      }
    },
    "/v1/web-calls": {
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebCall"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Web test call"
          }
        },
        "summary": "Create a non-PSTN browser web-call test session"
      }
    },
    "/v1/webhook-deliveries": {
      "get": {
        "description": "One bounded page, newest first by `created_at`, ties by creation order, as an object: the items under `deliveries` and `pagination`. Follow `pagination.next_cursor` while `pagination.has_more` is true; a walk returns every row that exists for the whole walk exactly once. `event_catalog` and `retry_policy` are returned with every page.",
        "parameters": [
          {
            "description": "Optional with a workspace key, which lists its own organization. An operator credential must name one: no list spans organizations.",
            "in": "query",
            "name": "organization_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Only this project's rows. A project of another organization is a 400.",
            "in": "query",
            "name": "project_id",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Page size, 1 to 500; 50 when omitted. A value outside that range or not a whole number is a 400, never shortened.",
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 500,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "The previous page's `pagination.next_cursor`, exactly as received. Anything else is a 400.",
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "deliveries": {
                      "description": "At most `limit` items, at most 500.",
                      "items": {
                        "type": "object"
                      },
                      "type": "array"
                    },
                    "event_catalog": {
                      "items": {
                        "type": "string"
                      },
                      "type": "array"
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/ListPagination"
                    },
                    "retry_policy": {
                      "type": "object"
                    }
                  },
                  "required": [
                    "deliveries",
                    "pagination",
                    "event_catalog",
                    "retry_policy"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "One page. Server-Timing and X-Skysay-Query-Count describe the read; Cache-Control is no-store."
          },
          "400": {
            "description": "`limit` outside its range or missing where required, a `cursor` that is not a `next_cursor`, no organization for an operator credential, or a project of another organization."
          }
        },
        "summary": "List real webhook delivery attempts (status, attempts, last error, next retry)"
      }
    },
    "/v1/webhooks/didit": {
      "post": {
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Webhook processing result"
          }
        },
        "summary": "didit.me status webhook (HMAC-signed, idempotent on event_id); Approved fetches the decision once"
      }
    },
    "/v1/worker-heartbeats": {
      "get": {
        "responses": {
          "200": {
            "description": "Worker heartbeats"
          }
        },
        "summary": "List worker heartbeats"
      },
      "post": {
        "responses": {
          "200": {
            "description": "Worker heartbeat"
          }
        },
        "summary": "Record a worker heartbeat"
      }
    },
    "/v1/worker-jobs": {
      "get": {
        "responses": {
          "200": {
            "description": "Worker jobs"
          }
        },
        "summary": "List background jobs"
      },
      "post": {
        "responses": {
          "201": {
            "description": "Worker job"
          }
        },
        "summary": "Queue a background job"
      }
    },
    "/v1/worker-jobs/claim": {
      "post": {
        "responses": {
          "200": {
            "description": "Claimed job"
          }
        },
        "summary": "Claim the next ready background job"
      }
    },
    "/v1/worker-jobs/{job_id}/call-artifact-import-preview": {
      "get": {
        "responses": {
          "200": {
            "description": "Call artifact import preview"
          }
        },
        "summary": "Preview a call artifact import worker job without importing files"
      }
    },
    "/v1/worker-jobs/{job_id}/call-artifact-import/execute": {
      "post": {
        "responses": {
          "200": {
            "description": "Call artifact import execution"
          }
        },
        "summary": "Execute a claimed call artifact import worker job"
      }
    },
    "/v1/worker-jobs/{job_id}/complete": {
      "post": {
        "responses": {
          "200": {
            "description": "Worker job"
          }
        },
        "summary": "Mark a job succeeded"
      }
    },
    "/v1/worker-jobs/{job_id}/fail": {
      "post": {
        "responses": {
          "200": {
            "description": "Worker job"
          }
        },
        "summary": "Mark a job failed or schedule retry"
      }
    },
    "/v1/worker-jobs/{job_id}/inbound-route-preview": {
      "get": {
        "responses": {
          "200": {
            "description": "Inbound route worker preview"
          }
        },
        "summary": "Preview an inbound live-media route worker handoff without bridging a call"
      }
    },
    "/v1/worker-jobs/{job_id}/inbound-route/execute": {
      "post": {
        "responses": {
          "200": {
            "description": "Inbound route worker execution"
          }
        },
        "summary": "Dry-run or complete a claimed inbound route worker job with a media handler"
      }
    },
    "/v1/worker-jobs/{job_id}/provider-sync-preview": {
      "get": {
        "responses": {
          "200": {
            "description": "Provider sync preview"
          }
        },
        "summary": "Preview a provider-sync worker job without running the provider read"
      }
    },
    "/v1/worker-jobs/{job_id}/provider-sync/execute": {
      "post": {
        "responses": {
          "200": {
            "description": "Provider sync execution"
          }
        },
        "summary": "Execute a claimed provider-sync worker job"
      }
    },
    "/v1/worker-jobs/{job_id}/retry": {
      "post": {
        "responses": {
          "200": {
            "description": "Worker job"
          }
        },
        "summary": "Retry a failed job"
      }
    },
    "/v1/workspace-context": {
      "get": {
        "description": "A tenant-bound bootstrap read for an organization-bound credential. The route derives the organization exclusively from the bearer principal and rejects organization_id rather than accepting an arbitrary tenant selector. It returns only organization id/name and project id/name/environment in stable created_at,id order, with an exact count and explicit page metadata. It works for an empty-agent starter workspace and never reads users, credentials, provider/operator data, or secrets; it makes no provider call and performs no write, repair, or lazy creation.",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "offset",
            "required": false,
            "schema": {
              "default": 0,
              "maximum": 100000,
              "minimum": 0,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkspaceContext"
                }
              }
            },
            "description": "Safe organization context and bounded project page"
          },
          "400": {
            "description": "organization_id was supplied or a page value is malformed"
          },
          "403": {
            "description": "Credential is not linked to an organization"
          },
          "404": {
            "description": "The organization bound to the credential no longer exists"
          }
        },
        "summary": "Discover the bearer API key's own organization and bounded projects",
        "x-required-scopes": [
          "account:read"
        ]
      }
    }
  },
  "servers": [
    {
      "url": "https://api.skysay.ai"
    }
  ]
}