{
 "updated": "2026-07-14",
 "specVersion": "VoiceML v0.9",
 "verbs": [
  {
   "slug": "say",
   "name": "Say",
   "xml": "<Say>",
   "status": "available",
   "summary": "Speak text to the caller with text-to-speech.",
   "body": "The text to speak. UTF-8.",
   "attributes": [
    {
     "name": "voice",
     "default": "engine default",
     "behaviour": "Text-to-speech voice override. Available voice names depend on the synthesis engine configured for your account."
    },
    {
     "name": "language",
     "default": "en",
     "behaviour": "BCP-47 language hint passed through to the configured text-to-speech engine."
    },
    {
     "name": "loop",
     "default": "1",
     "behaviour": "Number of times to repeat the text. 0 loops until the call hangs up."
    }
   ],
   "nested": [],
   "callbacks": [],
   "example": "<Response>\n  <Say voice=\"alice\" language=\"en-US\">Thanks for calling. Please hold while we connect you.</Say>\n</Response>",
   "notes": [
    "<Say> may also be nested inside <Gather> as a prompt and inside <Pay> prompts."
   ]
  },
  {
   "slug": "play",
   "name": "Play",
   "xml": "<Play>",
   "status": "available",
   "summary": "Play an audio file, or send touch-tone digits, to the caller.",
   "body": "The audio URL to play. https:// URLs are fetched and cached by the platform; a relative path resolves against the platform's static asset root.",
   "attributes": [
    {
     "name": "loop",
     "default": "1",
     "behaviour": "Number of times to play the file. 0 loops until the call hangs up."
    },
    {
     "name": "digits",
     "default": "unset",
     "behaviour": "Plays in-band touch-tone digits instead of (or after) audio. Charset 0-9 * # A-D w — each w is a 500 ms pause (wWww123 and similar strings work)."
    },
    {
     "name": "lengthSeconds",
     "default": "unset",
     "behaviour": "VoiceML extension. When set, execution waits this many seconds after queueing playback so a fixed-length asset is not cut off by the next verb."
    }
   ],
   "nested": [],
   "callbacks": [],
   "example": "<Response>\n  <Play loop=\"2\">https://cdn.example.com/audio/welcome.wav</Play>\n</Response>",
   "notes": [
    "Audio URLs are pre-fetched and cached content-addressed by upstream ETag — thousands of simultaneous calls playing the same URL collapse to a single upstream fetch. To force a refresh after re-uploading at the same URL, change the URL (fingerprinted filename or query parameter).",
    "Fetched bodies are validated against the URL's file extension (a .wav URL that serves HTML is rejected) and capped at 50 MiB.",
    "<Play> may also be nested inside <Gather> as a prompt and inside <Pay> prompts."
   ]
  },
  {
   "slug": "gather",
   "name": "Gather",
   "xml": "<Gather>",
   "status": "available",
   "summary": "Collect digits or speech from the caller and post the result to your server.",
   "body": null,
   "attributes": [
    {
     "name": "input",
     "default": "dtmf",
     "behaviour": "Input mode: dtmf, speech, or dtmf speech (tokens in any order, space or comma separated). In combined mode, whichever modality completes first wins; a key press during speech terminates collection immediately. Unrecognised tokens are ignored."
    },
    {
     "name": "action",
     "default": "current document URL",
     "behaviour": "URL that receives the result. When omitted, the current document URL is re-requested with the result fields — menu-style IVRs rely on this to re-render with the pressed digit. For inline documents with no source URL, an omitted action falls through to the next verb."
    },
    {
     "name": "method",
     "default": "POST",
     "behaviour": "HTTP method for the action request: POST or GET."
    },
    {
     "name": "numDigits",
     "default": "unlimited",
     "behaviour": "Maximum number of digits to collect. When unset, collection waits for finishOnKey or timeout."
    },
    {
     "name": "timeout",
     "default": "5",
     "behaviour": "Seconds of silence between digits before the gather returns."
    },
    {
     "name": "finishOnKey",
     "default": "#",
     "behaviour": "Digit that ends collection. Set to an empty string to disable."
    },
    {
     "name": "actionOnEmptyResult",
     "default": "false",
     "behaviour": "By default the action request is skipped when no input was captured and execution falls through to the next verb. Set true to post to the action URL even on empty input."
    },
    {
     "name": "speechTimeout",
     "default": "auto",
     "behaviour": "Seconds of trailing silence before speech is considered final. auto lets the recognizer decide when the caller has finished speaking."
    },
    {
     "name": "maxSpeechTime",
     "default": "30",
     "behaviour": "Hard cap on speech duration in seconds."
    },
    {
     "name": "language",
     "default": "en-US",
     "behaviour": "BCP-47 recognition language."
    },
    {
     "name": "speechModel",
     "default": "engine default",
     "behaviour": "Recognizer model hint, passed through to the configured speech engine."
    },
    {
     "name": "hints",
     "default": "unset",
     "behaviour": "Comma-separated phrases that bias recognition toward expected words."
    },
    {
     "name": "enhanced",
     "default": "false",
     "behaviour": "Opt into the premium recognition model where the configured engine offers one."
    },
    {
     "name": "profanityFilter",
     "default": "true",
     "behaviour": "Mask profanities in the transcript."
    },
    {
     "name": "partialResultCallback",
     "default": "unset",
     "behaviour": "URL that receives a fire-and-forget request for every interim transcript while the caller is still speaking. Interim results can arrive out of order — re-order on SequenceNumber when sequence matters. Delivery failures are logged, never propagated to the call."
    },
    {
     "name": "partialResultCallbackMethod",
     "default": "POST",
     "behaviour": "HTTP method for partial-result callbacks: POST or GET."
    }
   ],
   "nested": [
    "Say",
    "Play",
    "Pause"
   ],
   "callbacks": [
    {
     "name": "action request",
     "fields": [
      {
       "name": "Digits",
       "description": "The digits the caller pressed (DTMF input)."
      },
      {
       "name": "SpeechResult",
       "description": "The final recognized transcript (speech input)."
      },
      {
       "name": "Confidence",
       "description": "Recognition confidence between 0 and 1. Engines that do not report confidence return 0 — treat 0 as unknown."
      },
      {
       "name": "Language",
       "description": "The recognition language as a BCP-47 tag."
      },
      {
       "name": "CallSid",
       "description": "Unique call identifier, included with the standard call parameters (From, To, CallStatus, AccountSid)."
      }
     ]
    },
    {
     "name": "partialResultCallback request",
     "fields": [
      {
       "name": "CallSid",
       "description": "Unique call identifier."
      },
      {
       "name": "SpeechResult",
       "description": "The interim (in-progress) transcript for the current utterance."
      },
      {
       "name": "Confidence",
       "description": "Recognition confidence between 0 and 1, to four decimal places."
      },
      {
       "name": "Language",
       "description": "The recognition language as a BCP-47 tag."
      },
      {
       "name": "SequenceNumber",
       "description": "Zero-indexed counter scoped to the current <Gather>, used to re-order interim results."
      }
     ]
    }
   ],
   "example": "<Response>\n  <Gather input=\"dtmf speech\" numDigits=\"1\" timeout=\"5\"\n          action=\"https://app.example.com/menu\" method=\"POST\">\n    <Say>For sales, press 1 or say sales. For support, press 2 or say support.</Say>\n  </Gather>\n  <Redirect>https://app.example.com/no-input</Redirect>\n</Response>",
   "notes": [
    "Nested <Say>, <Play>, and <Pause> verbs play as the prompt. A digit pressed during the prompt is captured and counts as the first input; the prompt itself plays to completion rather than stopping early on the key press.",
    "Speech recognition uses the recognizer configured for your account: a streaming recognizer (interim results, ~100-300 ms finalization) or a file-based recognizer (~5-15 s, no interim results — partialResultCallback is silently ignored). When both are configured, streaming wins.",
    "Accounts without their own recognizer fall back to the platform default recognizer, which supports English only. A speech-mode gather with no available recognizer surfaces a clear speech-not-configured error rather than silently degrading."
   ]
  },
  {
   "slug": "record",
   "name": "Record",
   "xml": "<Record>",
   "status": "available",
   "summary": "Record the caller's audio and post the recording to your server.",
   "body": null,
   "attributes": [
    {
     "name": "action",
     "default": "unset",
     "behaviour": "URL that receives the recording result; the returned VoiceML continues the call."
    },
    {
     "name": "method",
     "default": "POST",
     "behaviour": "HTTP method for the action request: POST or GET."
    },
    {
     "name": "maxLength",
     "default": "3600",
     "behaviour": "Maximum recording length in seconds."
    },
    {
     "name": "timeout",
     "default": "5",
     "behaviour": "Silence timeout — seconds of quiet that ends the recording."
    },
    {
     "name": "finishOnKey",
     "default": "1234567890*#",
     "behaviour": "Any digit in this set ends the recording; the digit is reported in the Digits callback field."
    },
    {
     "name": "playBeep",
     "default": "true",
     "behaviour": "Plays a 500 ms 800 Hz beep before recording starts."
    },
    {
     "name": "trim",
     "default": "trim-silence",
     "behaviour": "trim-silence removes leading/trailing silence; do-not-trim keeps the raw take."
    },
    {
     "name": "recordingStatusCallback",
     "default": "unset",
     "behaviour": "URL that receives recording lifecycle events: in-progress, completed, absent."
    },
    {
     "name": "recordingStatusCallbackMethod",
     "default": "POST",
     "behaviour": "HTTP method for recording lifecycle callbacks."
    },
    {
     "name": "recordingStatusCallbackEvent",
     "default": "completed",
     "behaviour": "Comma-separated list of the lifecycle events to deliver: in-progress, completed, absent."
    },
    {
     "name": "transcribe",
     "default": "false",
     "behaviour": "true runs the configured speech recognizer over the finished recording and posts the transcript to transcribeCallback. Skipped silently when no recognizer is configured for the account."
    },
    {
     "name": "transcribeCallback",
     "default": "unset",
     "behaviour": "URL that receives the transcript (TranscriptionSid, TranscriptionText, TranscriptionStatus)."
    }
   ],
   "nested": [],
   "callbacks": [
    {
     "name": "action request",
     "fields": [
      {
       "name": "RecordingSid",
       "description": "Unique identifier of the recording."
      },
      {
       "name": "RecordingUrl",
       "description": "URL where the recording audio can be fetched."
      },
      {
       "name": "RecordingDuration",
       "description": "Length of the recording in seconds."
      },
      {
       "name": "Digits",
       "description": "The key the caller pressed to end the recording, when finishOnKey ended it."
      }
     ]
    },
    {
     "name": "transcribeCallback request",
     "fields": [
      {
       "name": "TranscriptionSid",
       "description": "Unique identifier of the transcription."
      },
      {
       "name": "TranscriptionText",
       "description": "The transcript text."
      },
      {
       "name": "TranscriptionStatus",
       "description": "completed or failed."
      }
     ]
    }
   ],
   "example": "<Response>\n  <Say>Please leave a message after the beep.</Say>\n  <Record maxLength=\"120\" timeout=\"5\" playBeep=\"true\"\n          action=\"https://app.example.com/voicemail\"\n          transcribe=\"true\" transcribeCallback=\"https://app.example.com/transcript\"/>\n</Response>",
   "notes": []
  },
  {
   "slug": "dial",
   "name": "Dial",
   "xml": "<Dial>",
   "status": "available",
   "summary": "Connect the caller to another party: a phone number, SIP endpoint, client, conference, queue, or saved application.",
   "body": "A bare phone number may be used as the body; nouns are the explicit form.",
   "attributes": [
    {
     "name": "action",
     "default": "unset",
     "behaviour": "URL requested when the dial completes; the returned VoiceML continues the call. Without an action, execution falls through to the next verb."
    },
    {
     "name": "method",
     "default": "POST",
     "behaviour": "HTTP method for the action request: POST or GET."
    },
    {
     "name": "timeout",
     "default": "30",
     "behaviour": "Seconds the dialled party has to answer."
    },
    {
     "name": "timeLimit",
     "default": "14400",
     "behaviour": "Maximum bridged-call duration in seconds."
    },
    {
     "name": "callerId",
     "default": "anonymous",
     "behaviour": "Caller ID presented to the dialled party."
    },
    {
     "name": "answerOnBridge",
     "default": "false",
     "behaviour": "true keeps the caller in ringing until the dialled party answers, then both legs answer simultaneously. Default: the caller's leg answers immediately and hears ringback while the far end is dialled."
    },
    {
     "name": "hangupOnStar",
     "default": "false",
     "behaviour": "true lets the caller press * to end the bridged leg and return to the document at the verb after <Dial> (the action callback fires with DialCallStatus=completed)."
    },
    {
     "name": "ringTone",
     "default": "unset",
     "behaviour": "Two-letter country code (us, gb, …) selecting the ringback tone the caller hears while the far end rings."
    },
    {
     "name": "sequential",
     "default": "false",
     "behaviour": "Accepted for compatibility but a no-op: one far-end leg is dialled per <Dial> (the first child)."
    },
    {
     "name": "record",
     "default": "do-not-record",
     "behaviour": "Records the bridged call. Values: do-not-record, record-from-answer, record-from-ringing, record-from-answer-dual, record-from-ringing-dual."
    },
    {
     "name": "recordingTrack",
     "default": "both",
     "behaviour": "Which side of the bridge is captured: inbound, outbound, or both."
    },
    {
     "name": "recordingStatusCallback",
     "default": "unset",
     "behaviour": "URL that receives recording lifecycle events (same shape as <Record>): in-progress, completed, absent."
    },
    {
     "name": "recordingStatusCallbackMethod",
     "default": "POST",
     "behaviour": "HTTP method for recording lifecycle callbacks."
    },
    {
     "name": "recordingStatusCallbackEvent",
     "default": "completed",
     "behaviour": "Comma-separated list of lifecycle events to deliver."
    }
   ],
   "nested": [
    "Number",
    "Sip",
    "Client",
    "Conference",
    "Queue",
    "Application"
   ],
   "callbacks": [
    {
     "name": "action request (Number / Sip / Client)",
     "fields": [
      {
       "name": "DialCallStatus",
       "description": "Outcome of the dial: completed, no-answer, busy, or failed."
      },
      {
       "name": "DialCallSid",
       "description": "Unique identifier of the dialled leg."
      },
      {
       "name": "DialCallDuration",
       "description": "Seconds the bridged call lasted."
      },
      {
       "name": "RecordingSid / RecordingUrl / RecordingDuration",
       "description": "Present when the bridge was recorded."
      }
     ]
    },
    {
     "name": "action request (Conference)",
     "fields": [
      {
       "name": "ConferenceSid",
       "description": "Identifier of the conference room the participant was in."
      },
      {
       "name": "DialCallStatus",
       "description": "completed on a clean leave, failed on a join error."
      },
      {
       "name": "DialCallDuration",
       "description": "Wall-clock seconds the participant was in the room."
      }
     ]
    }
   ],
   "example": "<Response>\n  <Dial callerId=\"+12125550100\" timeout=\"20\" record=\"record-from-answer\"\n        action=\"https://app.example.com/after-dial\">\n    <Number sendDigits=\"wwww1928\">+18005551234</Number>\n  </Dial>\n</Response>",
   "notes": [
    "<Sim> (mobile-SIM dialing) is not supported and returns an unsupported-element error."
   ]
  },
  {
   "slug": "enqueue",
   "name": "Enqueue",
   "xml": "<Enqueue>",
   "status": "available",
   "summary": "Place the caller into a named waiting queue.",
   "body": "The queue name (required). The queue is created on demand if it does not already exist.",
   "attributes": [
    {
     "name": "action",
     "default": "unset",
     "behaviour": "URL posted once when the call exits the queue — dequeued to an agent, left via <Leave>, or the caller hung up. The returned VoiceML replaces the rest of the original document. When empty, execution continues with the next verb after <Enqueue>."
    },
    {
     "name": "method",
     "default": "POST",
     "behaviour": "HTTP method for the action request: POST or GET."
    },
    {
     "name": "waitUrl",
     "default": "unset",
     "behaviour": "A VoiceML URL fetched in a loop while the caller waits, re-fetched every 30 seconds with fresh queue-position parameters so position announcements work. When empty, default hold music plays."
    },
    {
     "name": "waitUrlMethod",
     "default": "POST",
     "behaviour": "HTTP method used to fetch the wait URL: POST or GET."
    },
    {
     "name": "maxSize",
     "default": "0 (unlimited)",
     "behaviour": "VoiceML extension. Caps the queue size at creation time; when the cap is reached, <Enqueue> is rejected with a 503. Negative values are rejected."
    }
   ],
   "nested": [],
   "callbacks": [
    {
     "name": "action request",
     "fields": [
      {
       "name": "CallSid",
       "description": "Unique identifier of the queued call."
      },
      {
       "name": "QueueSid",
       "description": "Unique identifier of the queue."
      },
      {
       "name": "FriendlyName",
       "description": "The queue's friendly name."
      },
      {
       "name": "QueueResult",
       "description": "Why the call left the queue: bridged, hangup, leave, redirected, error, or system-error."
      },
      {
       "name": "QueueTime",
       "description": "How long the caller waited."
      }
     ]
    },
    {
     "name": "waitUrl request",
     "fields": [
      {
       "name": "CallSid",
       "description": "Unique identifier of the waiting call."
      },
      {
       "name": "QueueSid",
       "description": "Unique identifier of the queue."
      },
      {
       "name": "FriendlyName",
       "description": "The queue's friendly name."
      },
      {
       "name": "QueuePosition",
       "description": "The caller's current 1-based position; 1 is the front."
      },
      {
       "name": "QueueTime",
       "description": "How long this caller has been waiting."
      },
      {
       "name": "AvgQueueTime",
       "description": "Average wait over dequeue exits in the trailing 5-minute window; 0 when there are no recent exits."
      },
      {
       "name": "CurrentQueueSize",
       "description": "Number of callers currently waiting."
      }
     ]
    }
   ],
   "example": "<Response>\n  <Say>Placing you in the support queue.</Say>\n  <Enqueue waitUrl=\"https://app.example.com/queue-hold\"\n           action=\"https://app.example.com/queue-exit\">support</Enqueue>\n</Response>",
   "notes": [
    "The wait URL's returned document executes on the waiting caller's leg each iteration; a <Leave> inside it exits the queue.",
    "workflowSid is not supported — setting it returns an error.",
    "Queues are isolated per account: two accounts using the same queue name get independent queues, and cross-account access returns 404.",
    "Queues live in process memory: a platform restart drops waiting members, whose action URLs fire with QueueResult=system-error."
   ]
  },
  {
   "slug": "leave",
   "name": "Leave",
   "xml": "<Leave>",
   "status": "available",
   "summary": "Exit the current queue from inside an <Enqueue> wait loop.",
   "body": null,
   "attributes": [],
   "nested": [],
   "callbacks": [],
   "example": "<Response>\n  <Say>We are experiencing long waits. Please try again later.</Say>\n  <Leave/>\n</Response>",
   "notes": [
    "<Leave> takes no attributes and is meaningful only inside the VoiceML returned from an <Enqueue> waitUrl loop: the queue exits with QueueResult=leave, then either the <Enqueue> action URL runs (if set) or execution resumes at the verb after <Enqueue>.",
    "Outside a wait loop, <Leave> surfaces as a document error."
   ]
  },
  {
   "slug": "pause",
   "name": "Pause",
   "xml": "<Pause>",
   "status": "available",
   "summary": "Wait silently for a number of seconds.",
   "body": null,
   "attributes": [
    {
     "name": "length",
     "default": "1",
     "behaviour": "Seconds to wait before the next verb runs."
    }
   ],
   "nested": [],
   "callbacks": [],
   "example": "<Response>\n  <Say>Connecting you now.</Say>\n  <Pause length=\"2\"/>\n  <Dial>+18005551234</Dial>\n</Response>",
   "notes": [
    "<Pause> may also be nested inside <Gather> as part of the prompt."
   ]
  },
  {
   "slug": "hangup",
   "name": "Hangup",
   "xml": "<Hangup>",
   "status": "available",
   "summary": "End the call.",
   "body": null,
   "attributes": [],
   "nested": [],
   "callbacks": [],
   "example": "<Response>\n  <Say>Goodbye.</Say>\n  <Hangup/>\n</Response>",
   "notes": [
    "The call ends with normal clearing. For busy/rejected signalling, use <Reject>."
   ]
  },
  {
   "slug": "reject",
   "name": "Reject",
   "xml": "<Reject>",
   "status": "available",
   "summary": "Reject an incoming call without answering it.",
   "body": null,
   "attributes": [
    {
     "name": "reason",
     "default": "rejected",
     "behaviour": "rejected or busy. Selects the signalling the caller's carrier receives."
    }
   ],
   "nested": [],
   "callbacks": [],
   "example": "<Response>\n  <Reject reason=\"busy\"/>\n</Response>",
   "notes": [
    "Because the call is never answered, no charges accrue for the rejected call."
   ]
  },
  {
   "slug": "refer",
   "name": "Refer",
   "xml": "<Refer>",
   "status": "available",
   "summary": "Transfer the call to a SIP endpoint.",
   "body": "The SIP URI to transfer to — either as the element body or inside a nested <Sip> child.",
   "attributes": [
    {
     "name": "action",
     "default": "unset",
     "behaviour": "URL posted when the transfer completes; the returned VoiceML continues the call."
    },
    {
     "name": "method",
     "default": "POST",
     "behaviour": "HTTP method for the action request: POST or GET."
    }
   ],
   "nested": [
    "Sip"
   ],
   "callbacks": [
    {
     "name": "action request",
     "fields": [
      {
       "name": "ReferStatus",
       "description": "completed or failed."
      },
      {
       "name": "ReferCallSid",
       "description": "Unique identifier of the transfer leg."
      },
      {
       "name": "Cause",
       "description": "Failure cause, present when the transfer failed."
      }
     ]
    }
   ],
   "example": "<Response>\n  <Refer action=\"https://app.example.com/after-refer\">\n    <Sip>sip:support@pbx.example.com</Sip>\n  </Refer>\n</Response>",
   "notes": [
    "The platform implements the transfer as a locally bridged leg: the transfer target is dialled and bridged to the caller, which matches cold-transfer expectations end to end. Raw SIP REFER is not propagated to the carrier. Attended/consultative transfer flows are not in scope."
   ]
  },
  {
   "slug": "connect",
   "name": "Connect",
   "xml": "<Connect>",
   "status": "available",
   "summary": "Connect the call to a streaming destination: a raw audio WebSocket or an AI voice-agent relay.",
   "body": null,
   "attributes": [
    {
     "name": "action",
     "default": "unset",
     "behaviour": "URL posted when the connected session ends; the returned VoiceML continues the call. The posted fields depend on the noun: stream-result fields for <Stream>, RelayStatus/RelayDuration/HandoffData for <ConversationRelay>."
    },
    {
     "name": "method",
     "default": "POST",
     "behaviour": "HTTP method for the action request: POST or GET."
    }
   ],
   "nested": [
    "Stream",
    "ConversationRelay"
   ],
   "callbacks": [],
   "example": "<Response>\n  <Connect action=\"https://app.example.com/relay-done\">\n    <ConversationRelay url=\"wss://bot.example.com/relay\"\n                       welcomeGreeting=\"Hi! How can I help you today?\"/>\n  </Connect>\n</Response>",
   "notes": [
    "<Connect> blocks until the session ends (WebSocket closes or the call hangs up). For a non-blocking audio fork, use <Start><Stream>.",
    "Other nouns under <Connect> (hosted video rooms, hosted bot products) are not supported and return an unsupported-element error — use <ConversationRelay> for voice-AI integrations."
   ]
  },
  {
   "slug": "start",
   "name": "Start",
   "xml": "<Start>",
   "status": "available",
   "summary": "Start a background session — audio stream, live transcription, or SIP session recording — while the document continues.",
   "body": null,
   "attributes": [],
   "nested": [
    "Stream",
    "Transcription",
    "Siprec"
   ],
   "callbacks": [],
   "example": "<Response>\n  <Start>\n    <Stream url=\"wss://audio.example.com/ingest\" track=\"both_tracks\"/>\n  </Start>\n  <Dial>+18005551234</Dial>\n  <Stop>\n    <Stream/>\n  </Stop>\n</Response>",
   "notes": [
    "<Start> returns immediately: subsequent verbs run while the background session is live. End it with the matching <Stop> noun, or it ends when the call hangs up.",
    "An unknown noun under <Start> returns an unsupported-element error."
   ]
  },
  {
   "slug": "stop",
   "name": "Stop",
   "xml": "<Stop>",
   "status": "available",
   "summary": "Stop a background session started with <Start>.",
   "body": null,
   "attributes": [],
   "nested": [
    "Stream",
    "Transcription",
    "Siprec"
   ],
   "callbacks": [],
   "example": "<Response>\n  <Stop>\n    <Transcription/>\n  </Stop>\n</Response>",
   "notes": [
    "An unknown noun under <Stop> returns an unsupported-element error."
   ]
  },
  {
   "slug": "redirect",
   "name": "Redirect",
   "xml": "<Redirect>",
   "status": "available",
   "summary": "Fetch a new VoiceML document and continue the call with it.",
   "body": "The URL of the VoiceML document to fetch and execute.",
   "attributes": [
    {
     "name": "method",
     "default": "POST",
     "behaviour": "HTTP method for the document fetch: POST or GET."
    }
   ],
   "nested": [],
   "callbacks": [],
   "example": "<Response>\n  <Redirect method=\"GET\">https://app.example.com/next-step</Redirect>\n</Response>",
   "notes": [
    "Redirect chains are capped at 10 hops; hitting the limit surfaces a clear error rather than looping."
   ]
  },
  {
   "slug": "echo",
   "name": "Echo",
   "xml": "<Echo>",
   "status": "available",
   "summary": "Echo the caller's audio back to them — a media loopback test.",
   "body": null,
   "attributes": [],
   "nested": [],
   "callbacks": [],
   "example": "<Response>\n  <Echo/>\n</Response>",
   "notes": [
    "Runs until the call hangs up. Useful for latency and audio-path testing."
   ]
  },
  {
   "slug": "sms",
   "name": "Sms",
   "xml": "<Sms>",
   "status": "available",
   "summary": "Send an SMS as a side-effect of the voice call (legacy form of <Message>).",
   "body": "The message text.",
   "attributes": [
    {
     "name": "to",
     "default": "required",
     "behaviour": "E.164 destination number."
    },
    {
     "name": "from",
     "default": "unset",
     "behaviour": "Sending number. Falls back to the call's caller ID, then per-number routing, then the account default."
    },
    {
     "name": "action",
     "default": "unset",
     "behaviour": "Parsed and validated, but the action request is not yet dispatched. Message delivery state is visible on the Messages REST resource."
    },
    {
     "name": "method",
     "default": "POST",
     "behaviour": "Parsed; applies when action dispatch lands."
    },
    {
     "name": "statusCallback",
     "default": "unset",
     "behaviour": "Parsed and validated, but the status callback is not yet dispatched."
    }
   ],
   "nested": [],
   "callbacks": [],
   "example": "<Response>\n  <Sms to=\"+12125550100\">Thanks for calling — here is the link we discussed.</Sms>\n  <Say>We just texted you the link.</Say>\n</Response>",
   "notes": [
    "The call continues to the next verb regardless of send outcome. Every send creates a Message record (SM-prefixed identifier) queryable over REST.",
    "<Sms> is the legacy spelling; <Message> is the modern equivalent on the same send path."
   ]
  },
  {
   "slug": "message",
   "name": "Message",
   "xml": "<Message>",
   "status": "available",
   "summary": "Send an SMS or MMS as a side-effect of the voice call.",
   "body": "The message text (or nested <Body> / <Media> children).",
   "attributes": [
    {
     "name": "to",
     "default": "required",
     "behaviour": "E.164 destination number."
    },
    {
     "name": "from",
     "default": "unset",
     "behaviour": "Sending number. Falls back to the call's caller ID, then per-number routing, then the account default."
    },
    {
     "name": "action",
     "default": "unset",
     "behaviour": "Parsed and validated, but the action request is not yet dispatched. Message delivery state is visible on the Messages REST resource."
    },
    {
     "name": "method",
     "default": "POST",
     "behaviour": "Parsed; applies when action dispatch lands."
    },
    {
     "name": "statusCallback",
     "default": "unset",
     "behaviour": "Parsed and validated, but the status callback is not yet dispatched."
    }
   ],
   "nested": [],
   "callbacks": [],
   "example": "<Response>\n  <Message to=\"+12125550100\" from=\"+12125550199\">Your appointment is confirmed for 3 PM.</Message>\n</Response>",
   "notes": [
    "The call continues to the next verb regardless of send outcome. Every send creates a Message record (SM-prefixed identifier) queryable over REST.",
    "Media attachments (<Media> children) dispatch as MMS on the same path."
   ]
  },
  {
   "slug": "pay",
   "name": "Pay",
   "xml": "<Pay>",
   "status": "accepted",
   "summary": "Capture card payment details over the phone. Accepted and validated; capture runtime is enabled per account.",
   "body": null,
   "attributes": [
    {
     "name": "input",
     "default": "dtmf",
     "behaviour": "Input mode for card entry. dtmf only — spoken card entry is not available."
    },
    {
     "name": "action",
     "default": "current document URL",
     "behaviour": "URL that receives the payment result. When omitted, the current document URL is re-requested."
    },
    {
     "name": "actionOnEmptyResult",
     "default": "false",
     "behaviour": "Post to the action URL even when no input was captured."
    },
    {
     "name": "bankAccountType",
     "default": "consumer-checking",
     "behaviour": "Bank account type for ACH-style capture."
    },
    {
     "name": "cardType",
     "default": "unset",
     "behaviour": "Restrict accepted card networks."
    },
    {
     "name": "chargeAmount",
     "default": "unset",
     "behaviour": "Amount to charge; omit to tokenize only."
    },
    {
     "name": "currency",
     "default": "usd",
     "behaviour": "Charge currency."
    },
    {
     "name": "description",
     "default": "unset",
     "behaviour": "Charge description passed to the payment connector."
    },
    {
     "name": "language",
     "default": "en-us",
     "behaviour": "Language for the built-in prompts."
    },
    {
     "name": "maxAttempts",
     "default": "1",
     "behaviour": "How many retries the caller gets on invalid entry."
    },
    {
     "name": "minPostalCodeLength",
     "default": "unset",
     "behaviour": "Minimum postal-code length to accept."
    },
    {
     "name": "paymentConnector",
     "default": "Default",
     "behaviour": "Names the payment connector configured on your account that receives the captured payment method."
    },
    {
     "name": "paymentMethod",
     "default": "credit-card",
     "behaviour": "credit-card or ach-debit."
    },
    {
     "name": "postalCode",
     "default": "true",
     "behaviour": "Whether to prompt for a postal code."
    },
    {
     "name": "securityCode",
     "default": "true",
     "behaviour": "Whether to prompt for the card security code."
    },
    {
     "name": "statusCallback",
     "default": "unset",
     "behaviour": "URL receiving progress events during capture."
    },
    {
     "name": "statusCallbackMethod",
     "default": "POST",
     "behaviour": "HTTP method for status callbacks."
    },
    {
     "name": "timeout",
     "default": "5",
     "behaviour": "Seconds to wait for the caller's next digit."
    },
    {
     "name": "tokenType",
     "default": "reusable",
     "behaviour": "one-time or reusable tokenization."
    },
    {
     "name": "validCardTypes",
     "default": "visa mastercard amex",
     "behaviour": "Space-separated allow-list of card networks. Allowed values: visa, mastercard, amex, maestro, discover, optima, jcb, diners-club, enroute."
    }
   ],
   "nested": [
    "Prompt"
   ],
   "callbacks": [],
   "example": "<Response>\n  <Pay chargeAmount=\"20.45\" paymentConnector=\"my-connector\"\n       action=\"https://app.example.com/pay-result\">\n    <Prompt for=\"payment-card-number\">\n      <Say>Please enter your card number.</Say>\n    </Prompt>\n  </Pay>\n</Response>",
   "notes": [
    "All attributes and nested <Prompt> elements are parsed and validated. Live card capture requires payments to be enabled on your account with a configured payment connector — contact support to enable it.",
    "Nested <Prompt> elements customise the built-in prompts per capture step (for), error type (errorType), card network (cardType), and attempt number (attempt), with <Say>, <Play>, and <Pause> children."
   ]
  }
 ],
 "nouns": [
  {
   "slug": "number",
   "name": "Number",
   "xml": "<Number>",
   "parents": [
    "Dial"
   ],
   "status": "available",
   "summary": "Dial a phone number.",
   "body": "The E.164 phone number to dial.",
   "attributes": [
    {
     "name": "sendDigits",
     "default": "unset",
     "behaviour": "Touch-tone digits sent on the dialled leg after answer — extensions and access codes. Charset 0-9 * # A-D w; each w is a 500 ms pause."
    },
    {
     "name": "url",
     "default": "unset",
     "behaviour": "VoiceML fetched and executed on the dialled leg before the bridge — \"press 1 to accept\" screening prompts. Best-effort: a fetch failure is logged and the call proceeds."
    },
    {
     "name": "method",
     "default": "POST",
     "behaviour": "HTTP method for the screening-URL fetch."
    },
    {
     "name": "statusCallback",
     "default": "unset",
     "behaviour": "Per-leg lifecycle webhook URL. Currently reported through the parent <Dial> action callback; independent per-leg dispatch is on the roadmap."
    },
    {
     "name": "statusCallbackMethod",
     "default": "POST",
     "behaviour": "HTTP method for the per-leg webhook."
    },
    {
     "name": "statusCallbackEvent",
     "default": "unset",
     "behaviour": "Comma-separated list of leg lifecycle events to deliver."
    }
   ],
   "nested": [],
   "example": "<Response>\n  <Dial>\n    <Number sendDigits=\"wwww1928\">+18005551234</Number>\n  </Dial>\n</Response>",
   "notes": []
  },
  {
   "slug": "sip",
   "name": "Sip",
   "xml": "<Sip>",
   "parents": [
    "Dial",
    "Refer"
   ],
   "status": "available",
   "summary": "Dial a SIP URI directly, or name the transfer target of a <Refer>.",
   "body": "The SIP URI, e.g. sip:agent@pbx.example.com.",
   "attributes": [
    {
     "name": "username",
     "default": "unset",
     "behaviour": "SIP digest-auth username for the far end."
    },
    {
     "name": "password",
     "default": "unset",
     "behaviour": "SIP digest-auth password for the far end."
    },
    {
     "name": "sendDigits",
     "default": "unset",
     "behaviour": "Touch-tone digits sent after answer (same as <Number>)."
    },
    {
     "name": "url",
     "default": "unset",
     "behaviour": "VoiceML screening prompt run on the dialled leg before the bridge (same as <Number>)."
    },
    {
     "name": "method",
     "default": "POST",
     "behaviour": "HTTP method for the screening-URL fetch."
    },
    {
     "name": "statusCallback",
     "default": "unset",
     "behaviour": "Per-leg lifecycle webhook URL. Currently reported through the parent <Dial> action callback; independent per-leg dispatch is on the roadmap."
    },
    {
     "name": "statusCallbackMethod",
     "default": "POST",
     "behaviour": "HTTP method for the per-leg webhook."
    },
    {
     "name": "statusCallbackEvent",
     "default": "unset",
     "behaviour": "Comma-separated list of leg lifecycle events to deliver."
    }
   ],
   "nested": [],
   "example": "<Response>\n  <Dial>\n    <Sip username=\"agent1\" password=\"s3cret\">sip:agent@pbx.example.com</Sip>\n  </Dial>\n</Response>",
   "notes": [
    "The SIP leg is dialled directly — no carrier hop."
   ]
  },
  {
   "slug": "client",
   "name": "Client",
   "xml": "<Client>",
   "parents": [
    "Dial"
   ],
   "status": "available",
   "summary": "Dial a registered SIP client by identity.",
   "body": "The client identity — either as the bare body or a nested <Identity> child.",
   "attributes": [
    {
     "name": "sendDigits",
     "default": "unset",
     "behaviour": "Touch-tone digits sent after answer (same as <Number>)."
    },
    {
     "name": "url",
     "default": "unset",
     "behaviour": "VoiceML screening prompt run on the dialled leg before the bridge (same as <Number>)."
    },
    {
     "name": "method",
     "default": "POST",
     "behaviour": "HTTP method for the screening-URL fetch."
    },
    {
     "name": "statusCallback",
     "default": "unset",
     "behaviour": "Per-leg lifecycle webhook URL. Currently reported through the parent <Dial> action callback; independent per-leg dispatch is on the roadmap."
    },
    {
     "name": "statusCallbackMethod",
     "default": "POST",
     "behaviour": "HTTP method for the per-leg webhook."
    },
    {
     "name": "statusCallbackEvent",
     "default": "unset",
     "behaviour": "Comma-separated list of leg lifecycle events to deliver."
    }
   ],
   "nested": [
    "Identity"
   ],
   "example": "<Response>\n  <Dial>\n    <Client>\n      <Identity>joey</Identity>\n    </Client>\n  </Dial>\n</Response>",
   "notes": [
    "Both <Client>joey</Client> and the nested <Identity> form are accepted."
   ]
  },
  {
   "slug": "conference",
   "name": "Conference",
   "xml": "<Conference>",
   "parents": [
    "Dial"
   ],
   "status": "available",
   "summary": "Join the call into a named conference room.",
   "body": "The conference's friendly name. Rooms are isolated per account, so two accounts using the same name get independent rooms.",
   "attributes": [
    {
     "name": "muted",
     "default": "false",
     "behaviour": "true joins the participant muted — they hear the room but are not heard."
    },
    {
     "name": "beep",
     "default": "true",
     "behaviour": "A tone plays to the room when the participant joins or leaves; false suppresses it."
    },
    {
     "name": "startConferenceOnEnter",
     "default": "true",
     "behaviour": "false makes this a waiting participant who hears hold audio until a participant with startConferenceOnEnter=true arrives and the conference begins."
    },
    {
     "name": "endConferenceOnExit",
     "default": "false",
     "behaviour": "true ends the conference for everyone the moment this participant leaves."
    },
    {
     "name": "waitUrl",
     "default": "unset",
     "behaviour": "Audio played while waiting for the conference to start. Streaming sources and silence URIs are accepted verbatim."
    },
    {
     "name": "participantLabel",
     "default": "unset",
     "behaviour": "Friendly label visible in conference events and on the Participants REST resource; also the value another participant's coach attribute matches against."
    },
    {
     "name": "maxParticipants",
     "default": "unset",
     "behaviour": "Per-join hint for the room's maximum size; the hard cap is set at platform level."
    },
    {
     "name": "whisper",
     "default": "unset",
     "behaviour": "URL fetched just before the join; the returned VoiceML runs privately on the joining participant's leg (\"you have a call from…, press 1 to accept\"). Best-effort: a fetch failure does not abort the join."
    },
    {
     "name": "record",
     "default": "do-not-record",
     "behaviour": "record-from-start records the conference mix; recording stops when this participant leaves."
    },
    {
     "name": "trim",
     "default": "trim-silence",
     "behaviour": "Accepted for compatibility; currently a no-op for conference recordings."
    },
    {
     "name": "recordingStatusCallback",
     "default": "unset",
     "behaviour": "URL receiving recording lifecycle events: in-progress, completed, absent (same shape as <Record>)."
    },
    {
     "name": "recordingStatusCallbackMethod",
     "default": "POST",
     "behaviour": "HTTP method for recording lifecycle callbacks."
    },
    {
     "name": "recordingStatusCallbackEvent",
     "default": "completed",
     "behaviour": "Comma-separated list of recording lifecycle events to deliver."
    },
    {
     "name": "statusCallback",
     "default": "unset",
     "behaviour": "URL receiving live conference status events."
    },
    {
     "name": "statusCallbackEvent",
     "default": "all events",
     "behaviour": "Comma-separated allow-list of conference event names to deliver; omit to receive all. See the event vocabulary below."
    },
    {
     "name": "coach",
     "default": "unset",
     "behaviour": "Set to another participant's participantLabel to join as a whisper-coach: hear everyone, be heard only by the coached participant."
    },
    {
     "name": "region",
     "default": "unset",
     "behaviour": "Accepted for compatibility; a no-op on this single-region platform."
    }
   ],
   "nested": [],
   "example": "<Response>\n  <Dial action=\"https://app.example.com/conf-exit\">\n    <Conference startConferenceOnEnter=\"false\"\n                waitUrl=\"https://cdn.example.com/hold.wav\"\n                statusCallback=\"https://app.example.com/conf-events\"\n                participantLabel=\"customer\">support-room</Conference>\n  </Dial>\n</Response>",
   "notes": [
    "Conference status events: conference-start, conference-end, participant-join, participant-leave, participant-mute, participant-unmute, participant-hold, participant-unhold, participant-speech-start, participant-speech-stop. Match on the full event strings.",
    "Friendly names containing a double underscore (__), whitespace, control characters, or the URL metacharacters / ? # are rejected.",
    "When the participant leaves, the parent <Dial> action fires with ConferenceSid, DialCallStatus (completed on a clean leave, failed on a join error), and DialCallDuration (seconds in the room)."
   ]
  },
  {
   "slug": "queue",
   "name": "Queue",
   "xml": "<Queue>",
   "parents": [
    "Dial"
   ],
   "status": "available",
   "summary": "Bridge the caller (typically an agent) to the front-of-queue waiting caller.",
   "body": "The queue name.",
   "attributes": [
    {
     "name": "url",
     "default": "unset",
     "behaviour": "VoiceML run on the dequeued caller's leg before the two parties are bridged — \"you're now connected to an agent.\""
    }
   ],
   "nested": [],
   "example": "<Response>\n  <Say>Connecting you to the next caller.</Say>\n  <Dial>\n    <Queue url=\"https://app.example.com/about-to-connect\">support</Queue>\n  </Dial>\n</Response>",
   "notes": [
    "The longest-waiting caller is popped from the queue, the optional url document runs on their leg, then the legs bridge.",
    "The parent <Dial> returns the standard result: an empty queue plus dial timeout yields no-answer.",
    "The same dequeue can be driven over REST via the queue's Members/Front endpoint with a Url form parameter."
   ]
  },
  {
   "slug": "application",
   "name": "Application",
   "xml": "<Application>",
   "parents": [
    "Dial"
   ],
   "status": "available",
   "summary": "Hand the call leg to a saved Application's voice URL.",
   "body": "The Application identifier (AP followed by 32 hex characters).",
   "attributes": [
    {
     "name": "url",
     "default": "unset",
     "behaviour": "Overrides the Application's saved voice URL for this single dial — one-off variations without creating a new Application."
    }
   ],
   "nested": [],
   "example": "<Response>\n  <Dial>\n    <Application>APxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx</Application>\n  </Dial>\n</Response>",
   "notes": [
    "The Application's voice URL is fetched with CallSid, ApplicationSid, AccountSid, and From (the Dial's callerId); the returned VoiceML replaces the rest of the calling document for that leg. On fetch failure, the Application's fallback voice URL is tried.",
    "The verb returns a standard dial result: completed on successful execution, failed on lookup or fetch failure. An Application owned by another account behaves exactly like a nonexistent one."
   ]
  },
  {
   "slug": "sim",
   "name": "Sim",
   "xml": "<Sim>",
   "parents": [
    "Dial"
   ],
   "status": "unsupported",
   "summary": "Mobile-SIM dialing. Not supported.",
   "body": null,
   "attributes": [],
   "nested": [],
   "example": null,
   "notes": [
    "<Dial><Sim> returns an unsupported-element error. Mobile-SIM backends are intentionally out of scope."
   ]
  },
  {
   "slug": "stream",
   "name": "Stream",
   "xml": "<Stream>",
   "parents": [
    "Connect",
    "Start",
    "Stop"
   ],
   "status": "available",
   "summary": "Fork the call audio to your WebSocket server.",
   "body": null,
   "attributes": [
    {
     "name": "url",
     "default": "required",
     "behaviour": "wss:// (or ws:// in test environments) endpoint that receives the audio fork."
    },
    {
     "name": "track",
     "default": "both_tracks",
     "behaviour": "Which audio to fork: inbound_track, outbound_track, or both_tracks."
    },
    {
     "name": "name",
     "default": "unset",
     "behaviour": "Operator-friendly identifier echoed in logs and callbacks. One audio fork is active per call, so <Stop><Stream> targets the running fork regardless of name."
    },
    {
     "name": "statusCallback",
     "default": "unset",
     "behaviour": "Lifecycle webhook fired on stream start, stop, and failure."
    },
    {
     "name": "statusCallbackMethod",
     "default": "POST",
     "behaviour": "HTTP method for lifecycle webhooks."
    }
   ],
   "nested": [],
   "example": "<Response>\n  <Connect action=\"https://app.example.com/stream-done\">\n    <Stream url=\"wss://audio.example.com/ingest\" track=\"inbound_track\"/>\n  </Connect>\n</Response>",
   "notes": [
    "Under <Connect>, the stream blocks until the WebSocket closes or the call hangs up, then posts StreamSid, StreamDuration, and StreamStatus to the <Connect> action URL.",
    "Under <Start>, the fork is non-blocking: subsequent verbs run while it is live; <Stop><Stream/> or call hangup ends it."
   ]
  },
  {
   "slug": "conversation-relay",
   "name": "ConversationRelay",
   "xml": "<ConversationRelay>",
   "parents": [
    "Connect"
   ],
   "status": "available",
   "summary": "Bridge the call to your AI voice agent over a JSON WebSocket — the platform runs speech recognition and synthesis in-line.",
   "body": null,
   "attributes": [
    {
     "name": "url",
     "default": "required",
     "behaviour": "wss:// endpoint of your application's JSON WebSocket. Your endpoint receives finalized transcripts and replies with text to speak — it never touches raw audio."
    },
    {
     "name": "voice",
     "default": "engine default",
     "behaviour": "Synthesis voice used when speaking your application's responses."
    },
    {
     "name": "language",
     "default": "en-US",
     "behaviour": "Language for the session's speech recognition and synthesis."
    },
    {
     "name": "welcomeGreeting",
     "default": "unset",
     "behaviour": "Greeting spoken to the caller when the relay session begins."
    }
   ],
   "nested": [],
   "example": "<Response>\n  <Connect action=\"https://app.example.com/relay-done\">\n    <ConversationRelay url=\"wss://bot.example.com/relay\"\n                       voice=\"alice\" language=\"en-US\"\n                       welcomeGreeting=\"Hi! How can I help you today?\"/>\n  </Connect>\n</Response>",
   "notes": [
    "Messages your server receives: setup (once, with sessionId / callSid / language), prompt (each finalized caller utterance: voicePrompt / lang / last), interrupt (caller spoke over playing audio: utteranceUntilInterrupt / durationUntilInterruptMs / latestUserPartial), dtmf (keypress: digit).",
    "Messages your server sends: text (token to synthesize; interruptible defaults true, preemptible defaults false, last marks the final segment), play (source URL, same interruptible/preemptible flags), end (optional handoffData surfaced verbatim on the action callback).",
    "preemptible=true lets a newer text/play message abort an in-flight playback — useful when your language model corrects itself mid-response.",
    "When the relay ends, the <Connect> action URL receives RelayStatus (completed/failed), RelayDuration (seconds), and HandoffData; the returned VoiceML runs on the still-live call — redirect to an agent or hang up.",
    "The relay needs both a speech recognizer and a synthesis engine configured on your account (or the platform defaults); missing configuration surfaces a clear error before any WebSocket connection is attempted.",
    "Not yet available: mark-event sync points, mid-session language switching, and play loop counts greater than 1."
   ]
  },
  {
   "slug": "transcription",
   "name": "Transcription",
   "xml": "<Transcription>",
   "parents": [
    "Start",
    "Stop"
   ],
   "status": "available",
   "summary": "Live transcription of the call, delivered to your webhook while the document keeps executing.",
   "body": null,
   "attributes": [
    {
     "name": "name",
     "default": "unset",
     "behaviour": "Operator-friendly identifier for the session, echoed on every callback as Name."
    },
    {
     "name": "languageCode",
     "default": "en-US",
     "behaviour": "BCP-47 language tag passed to the transcription engine."
    },
    {
     "name": "transcriptionEngine",
     "default": "account default",
     "behaviour": "Selects the transcription engine (case-insensitive): deepgram, google, aws, or azure. Unknown values fall through to the account default."
    },
    {
     "name": "partialResults",
     "default": "false",
     "behaviour": "true also delivers interim transcripts to the status callback; the default delivers final transcripts only."
    },
    {
     "name": "track",
     "default": "both_tracks",
     "behaviour": "Which audio to transcribe: inbound_track, outbound_track, or both_tracks. Passed to the engine as a hint; engine support varies."
    },
    {
     "name": "statusCallback",
     "default": "unset",
     "behaviour": "URL receiving transcript content and lifecycle events as form-encoded requests."
    },
    {
     "name": "statusCallbackMethod",
     "default": "POST",
     "behaviour": "HTTP method for status callbacks."
    },
    {
     "name": "statusCallbackEvent",
     "default": "all events",
     "behaviour": "Comma-separated filter: transcription-started, transcription-content, transcription-stopped, transcription-error. Omit to receive all."
    },
    {
     "name": "hints",
     "default": "unset",
     "behaviour": "Passed through to the engine to bias recognition."
    },
    {
     "name": "profanityFilter",
     "default": "unset",
     "behaviour": "Passed through to the transcription engine."
    }
   ],
   "nested": [],
   "example": "<Response>\n  <Start>\n    <Transcription statusCallback=\"https://app.example.com/transcripts\"\n                   partialResults=\"true\" track=\"both_tracks\"/>\n  </Start>\n  <Dial>+18005551234</Dial>\n</Response>",
   "notes": [
    "Callback fields: CallSid, TranscriptionEvent, Transcript, Confidence (0-1, four decimal places), Language, IsFinal (true for final, false for interim), plus Name and Track when set.",
    "Unlike speech-mode <Gather> (listen once, then return), <Start><Transcription> runs long-lived and in parallel with the rest of the document.",
    "One active transcription per call: a second <Start><Transcription> replaces the first. <Stop><Transcription/> ends it early; otherwise it ends at hangup."
   ]
  },
  {
   "slug": "siprec",
   "name": "Siprec",
   "xml": "<Siprec>",
   "parents": [
    "Start",
    "Stop"
   ],
   "status": "available",
   "summary": "Stream both legs of the call to your session-recording server over the SIPREC standard (RFC 7866).",
   "body": null,
   "attributes": [
    {
     "name": "connectorName",
     "default": "required",
     "behaviour": "Names a SIPREC connector configured on your account (portal → Recordings). The connector resolves to your Session Recording Server's SIP URI. An empty, unknown, or disabled connector starts no recording — the verb fails closed."
    },
    {
     "name": "name",
     "default": "unset",
     "behaviour": "Operator identifier, logged and echoed in callbacks."
    },
    {
     "name": "track",
     "default": "both_tracks",
     "behaviour": "inbound_track, outbound_track, or both_tracks."
    },
    {
     "name": "statusCallback",
     "default": "unset",
     "behaviour": "Lifecycle webhook: started, stopped, error."
    },
    {
     "name": "statusCallbackMethod",
     "default": "POST",
     "behaviour": "HTTP method for lifecycle webhooks."
    }
   ],
   "nested": [
    "Parameter"
   ],
   "example": "<Response>\n  <Start>\n    <Siprec name=\"audit-trail\" connectorName=\"srs1\" track=\"both_tracks\"\n            statusCallback=\"https://app.example.com/siprec\">\n      <Parameter name=\"vendor\" value=\"acme\"/>\n    </Siprec>\n  </Start>\n  <Dial><Number>+18005551234</Number></Dial>\n  <Stop><Siprec/></Stop>\n</Response>",
   "notes": [
    "Nested <Parameter name value> pairs pass through to the SIPREC INVITE and surface on the status callback as Parameter.<name> form fields.",
    "One active SIPREC session per call: a second <Start><Siprec> stops the prior session before starting the new one. <Stop><Siprec/> ends it; otherwise it ends at hangup."
   ]
  },
  {
   "slug": "parameter",
   "name": "Parameter",
   "xml": "<Parameter>",
   "parents": [
    "Siprec"
   ],
   "status": "available",
   "summary": "A name/value pair passed through to the parent element's session.",
   "body": null,
   "attributes": [
    {
     "name": "name",
     "default": "required",
     "behaviour": "Parameter name."
    },
    {
     "name": "value",
     "default": "required",
     "behaviour": "Parameter value."
    }
   ],
   "nested": [],
   "example": "<Parameter name=\"vendor\" value=\"acme\"/>",
   "notes": [
    "Surfaced on the parent session's status callback as Parameter.<name> form fields."
   ]
  },
  {
   "slug": "identity",
   "name": "Identity",
   "xml": "<Identity>",
   "parents": [
    "Client"
   ],
   "status": "available",
   "summary": "The client identity to dial, as a nested element.",
   "body": "The registered client identity.",
   "attributes": [],
   "nested": [],
   "example": "<Client>\n  <Identity>joey</Identity>\n</Client>",
   "notes": [
    "Equivalent to the bare-body form <Client>joey</Client>."
   ]
  },
  {
   "slug": "prompt",
   "name": "Prompt",
   "xml": "<Prompt>",
   "parents": [
    "Pay"
   ],
   "status": "available",
   "summary": "Customise a built-in <Pay> prompt for a specific capture step.",
   "body": null,
   "attributes": [
    {
     "name": "for",
     "default": "unset",
     "behaviour": "Which capture step this prompt replaces: payment-card-number, expiration-date, security-code, postal-code, bank-routing-number, bank-account-number, or payment-processing."
    },
    {
     "name": "errorType",
     "default": "unset",
     "behaviour": "Restrict the prompt to a specific error condition."
    },
    {
     "name": "cardType",
     "default": "unset",
     "behaviour": "Restrict the prompt to a specific card network."
    },
    {
     "name": "attempt",
     "default": "unset",
     "behaviour": "Restrict the prompt to a specific attempt number."
    }
   ],
   "nested": [
    "Say",
    "Play",
    "Pause"
   ],
   "example": "<Pay paymentConnector=\"my-connector\">\n  <Prompt for=\"payment-card-number\" attempt=\"2\">\n    <Say>That number didn't validate. Please re-enter your card number.</Say>\n  </Prompt>\n</Pay>",
   "notes": []
  }
 ],
 "platform": {
  "unknownElements": "Any unknown top-level element (typos like <Saw>, or vendor-specific extensions from other platforms) returns a typed unsupported-element error, visible in logs and in the parent verb's action callback when one fires.",
  "urlValidation": "Every URL attribute (action URLs, callback URLs, audio URLs, stream URLs) is validated against the RFC 3986 printable-ASCII charset before use; CR/LF and backslash are rejected outright. Webhook fetches are HTTPS-only, verified against the system CA bundle.",
  "webhookSigning": "Every callback and document fetch is signed with your account's API key using the industry-standard request-signature scheme, so stock webhook-validation middleware in the official helper libraries accepts VoiceML requests without modification.",
  "caching": {
   "hosted": "Markup published to voiceml.org — the free VoiceML hosting service — is content-addressed: the URL is derived from a hash of the markup itself, so the content behind a given URL is permanent and can never change. It is served with a strong ETag and Cache-Control: max-age=31536000, immutable, and the platform caches it accordingly — effectively forever. Publishing identical markup always returns the same URL; to change what a call does, publish the new markup and point your number at the new URL.",
   "audio": "Audio played by <Play> is pre-fetched to a local cache, deduplicated by upstream ETag, validated by content type, and capped at 50 MiB per file. Change the URL (fingerprinted name or query parameter) to force a refresh.",
   "documents": "A VoiceML document served from your own server is cached only when that server opts in: Cache-Control: max-age=N caches the parsed document for N seconds; an ETag enables a 5-second burst-dedup window with If-None-Match revalidation; Cache-Control: no-store / no-cache (or no caching headers at all) means every call fetches fresh. Cache entries are keyed per account, so signing continues to work as expected.",
   "initialFetch": "The initial voice-URL fetch does not carry call-context fields (CallSid, From, To). A server that must branch per-call on first fetch should encode identifying information into the voice URL itself, or decide from an action callback later in the flow, which does carry full call context."
  }
 }
}
