Skip to content

fix(swml): align the SWML method reference with platform behavior - #852

Open
Devon-White wants to merge 55 commits into
mainfrom
devon/implement-validated-swml-audit-findings
Open

Devon-White wants to merge 55 commits into
mainfrom
devon/implement-validated-swml-audit-findings

Conversation

@Devon-White

@Devon-White Devon-White commented Oct 25, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

This PR corrects the SWML method reference for calling and messaging. Each method's documented parameters, types, defaults, limits, results and callbacks now match how SignalWire runs SWML. The TypeSpec sources, the reference pages, and the generated calling and messaging JSON Schemas are updated together, so validators built on the schemas agree with the docs.

What changes in the docs

  • Control flow (cond, execute, goto, label, request, return, set, unset, switch, transfer, user_event)
    • Label and variable-name rules, and how goto counts jumps and when it stops.
    • How cond picks a branch: else can't appear beside when/then in the same entry, and there can be at most one else.
    • request timeout defaults, header rules and output variables.
    • Which value types return accepts and how nesting depth is limited.
    • Supported transfer destinations.
  • Telephony (answer, hangup, sleep, send_digits, send_sms, send_fax, receive_fax, sip_refer)
    • answer accepts codec lists, and hangup lists all six reasons.
    • sleep no longer accepts 0 or values below -1.
    • Digit rules and the 99-character limit for send_digits.
    • send_sms requires non-empty numbers and a text body.
    • Callback field paths and error events for fax.
    • The to alias and the REFER states for sip_refer.
  • Media (play, prompt, record, record_call, stop_record_call)
    • play loop, volume and text-to-speech settings, plus play_result.
    • prompt inputs limited to supported values, with corrected outputs and callbacks.
    • record supports direction both; sensitivity bounds and the max_length default for both recording methods.
    • Accurate beep and callback behavior.
  • Streaming and transcription (stream, stop_stream, tap, transcribe, live_transcribe, live_translate, denoise, stop_denoise, detect_machine)
    • tap rtp_ptime limits, and RTP targets must be public IPv4 addresses.
    • Voice activity detection defaults for live_transcribe and live_translate, and the order of translation directions.
    • start.hints for live_transcribe.
    • Variable references wherever they're supported.
    • Clearer result variables and callback payloads.
  • Conferences, rooms, queues and payments (connect, join_conference, join_room, enter_queue, pay)
    • connect documents stop_all_on_reject, ringback and codecs, and its confirm cond follows the same rules as cond.
    • join_conference drops result and adds min_participants and emit_call_quality.
    • enter_queue documents whisper_url and corrects queue results.
    • pay documents ACH payments and its result variables.
  • AI (ai, amazon_bedrock, ai_sidecar)
    • Saved agents (agent).
    • Hook names corrected to startup_hook and hangup_hook.
    • Contexts and steps: reset, gather_info and top-level steps.
    • Parameter defaults and ranges, including parameters that have no effect.
    • SWAIG data_map, actions and function parameters.
    • amazon_bedrock now lists only the prompt, params, functions and data_map fields it supports.
    • ai_sidecar corrects transcription, permissions, actions and tuning, and documents summarize requests, which don't need lang.
  • Messaging (reply, request, switch, execute, goto, transfer, return)
    • reply requires content; length limits, and queued vs. delivered status.
    • request results and body serialization.
    • How switch matches cases.
    • URL and inline destinations for execute and transfer.

The REST API reference changes only where it embeds SWML definitions. That covers SWML-based request bodies, the AI agent hints description, and calling.ai_sidecar, which now also accepts a summarize request.

Documents the schemas now reject

These changes affect schema validation only, for example editor tooling that uses the published JSON Schemas. Each change rejects input SignalWire already rejects, ignores or reads differently than the old docs suggested:

  • ai
    • Variable references are no longer accepted in AI settings that are read literally. These include numeric and boolean params, pronounce.ignore_case, POM numbered, function active, and webhook input_args_as_params and foreach.max.
    • digit_timeout must be between 1 and 29,999 ms, and hard_stop_time doesn't accept unit suffixes.
    • Context-level text is replaced by prompt.
    • data_map webhooks accept only GET and POST, and header values must be strings.
    • toggle_functions.function takes a single name, and unset_global_data/unset_meta_data don't accept an object.
  • amazon_bedrock no longer accepts ai-only fields:
    • prompt confidence, max_tokens and penalties, and post-prompt tuning
    • function active, the say and hangup actions, and webhook require_args
    • attention_timeout above 60,000 ms
  • ai_sidecar: direction must list both call legs.
  • cond, execute results and the connect confirm cond: no else beside when/then, and no more than one else.
  • label and goto reject an empty label. set and unset accept only plain variable names. sleep rejects 0 and values below -1. transfer rejects an empty dest. request header values must be strings.
  • connect rejects:
    • empty parallel, serial and serial_parallel lists
    • header names shorter than 3 characters, and empty header values
    • caller ID names longer than 70 characters or containing unsupported characters
    • answer inside a confirm cond
  • prompt rejects:
    • unknown speech engines and unsupported say_gender values
    • too-short timeouts and max_digits: 0
    • malformed language tags and voice names
    • a status_url that doesn't use HTTP or HTTPS
    • unknown ringtones and empty say text
    • silence or ring durations under 1 second (the silence limit also applies to play)
  • record and record_call: input_sensitivity must be 0–100. In record_call, control_id can't contain { } , ; ' \ or line breaks outside ${...}/%{...} references.
  • Other calling methods
    • join_conference no longer accepts result.
    • live_translate direction needs at least one entry.
    • pay rejects:
      • literal booleans for security_code and postal_code
      • attempts on a payment prompt (the field is attempt)
      • unknown prompt targets
      • an http:// payment_connector_url
    • send_sms rejects empty numbers and an empty text-only body.
    • tap rtp_ptime must be a multiple of 10 between 10 and 120.
  • Messaging reply rejects an empty string, or an empty media list with no body, including inside inline switch outcomes.

Linked issues

Not included

The older combined schema file specs/swml/tsp-output/@typespec/json-schema/SWMLObject.json is no longer produced by the build, and this PR doesn't update it. The maintained schemas are specs/swml/calling/... and specs/swml/messaging/....

Checks

  • yarn build:specs passes, and a rebuild reproduces the committed generated files.
  • yarn fern-md-check passes.
  • YAML and JSON examples on the changed pages validate against the regenerated schemas, and each YAML/JSON pair is equivalent.

…sult timing

cond: a when/then entry can no longer carry its own else; the else
fallback is a standalone entry, at most one per list, and the schema now
enforces both. The reference explains first-match selection, fallback and
no-match continuation, and the user-input example tests the collected
digit instead of the call type.

execute: result runs after the section or URL returns and before
on_return, only when a return_value was set; the object/array choice is
made on the shape of result itself. params documents the 63-level nesting
limit. The hosted-server example keeps some_info numeric and reads the
parameter it actually sends.

Audit keys:
- calling/cond::SWML-COND-001
- calling/cond::SWML-COND-002
- calling/cond::SWML-COND-003
- calling/cond::SWML-COND-004
- calling/execute::SWML-EXECUTE-001
- calling/execute::SWML-EXECUTE-002
- calling/execute::SWML-EXECUTE-003
- calling/execute::SWML-EXECUTE-004
- calling/execute::SWML-EXECUTE-005
- calling/execute::SWML-EXECUTE-006
…variables

timeout and connect_timeout default to 5 seconds (0 uses the default,
values above 30 are capped), not "no timeout". headers is a map of
header names to string values; the page now says which names are allowed
and which characters a value may contain, and that invalid entries are
skipped. method accepts a SWML variable reference. request_result is
defined by the 2xx response code, return_value is no longer listed as an
output, the unsupported 64-header response limit is gone, and the
62-level nesting limit for saved responses is documented.

Audit keys:
- calling/request::SWML-REQUEST-001
- calling/request::SWML-REQUEST-002
- calling/request::SWML-REQUEST-003
- calling/request::SWML-REQUEST-004
- calling/request::SWML-REQUEST-005
- calling/request::SWML-REQUEST-006
- calling/request::SWML-REQUEST-007
- calling/request::SWML-REQUEST-008
label: names must be nonempty (now enforced by the schema) and unique
within the section. goto.label gets its own description of the label it
jumps to instead of inheriting the label method's text. goto.max is
documented as accepting a SWML variable reference, as the schema already
allowed, and explains that the jump count belongs to the target label and
is shared by every goto to it.

Audit keys:
- calling/label::SWML-LABEL-001
- calling/label::SWML-LABEL-002
- calling/goto::SWML-GOTO-002
- calling/goto::SWML-GOTO-003
- calling/goto::SWML-GOTO-004
…and depth limit

return takes a string, number, object or array; booleans and null only
work nested or as the result of a variable reference. The page now
explains that variables are resolved in string values throughout the
returned data (keys stay literal, a lone reference keeps its JSON type),
that resolved values deeper than 62 object/array levels are not stored,
and adds an example returning an object built from variables.

Audit keys:
- calling/return::SWML-RETURN-001
- calling/return::SWML-RETURN-002
- calling/return::SWML-RETURN-003
set and unset only accept identifier-style variable names (an ASCII
letter or underscore, then up to 255 letters, digits or underscores,
with an optional vars. prefix). A shared SetVariableName scalar now
validates set's keys and unset's names in the schema. set documents the
62-level nesting limit for assigned values; unset explains that it
removes whole top-level variables of the current call and cannot target
child paths or other namespaces.

Audit keys:
- calling/set::SWML-SET-001
- calling/set::SWML-SET-002
- calling/unset::SWML-UNSET-001
- calling/unset::SWML-UNSET-002
The case entry is described as a value-to-methods mapping under
switch.case rather than a second variable name. The page and schema now
say the inspected value is converted to a string before lookup (so 5
matches "5"), and that with no match and no default no branch runs, a
relay_script_method_no_matching_condition warning is reported and
script_error is set, documented in a new Variables section.

Audit keys:
- calling/switch::SWML-SWITCH-001
- calling/switch::SWML-SWITCH-002
- calling/switch::SWML-SWITCH-003
…uto-answer

event is documented as the payload of one custom event (an object), and
can also be a SWML variable reference that resolves to an event object;
the schema now allows that form. The page explains that string values in
the payload are expanded, that SignalWire adds call details at the top
level so custom data belongs under its own key, and that the method
answers an unanswered inbound call before sending. The multi-field
example is retitled as one event, and a variable-payload example is added.

Audit keys:
- calling/user_event::SWML-USER_EVENT-001
- calling/user_event::SWML-USER_EVENT-002
- calling/user_event::SWML-USER_EVENT-003
- calling/user_event::SWML-USER_EVENT-004
- calling/user_event::SWML-USER_EVENT-005
…ocument dest and params limits

- Remove Relay application and inline JSON document destinations; state that
  relay: destinations are not supported.
- Require a nonempty dest (minLength 1) and document that a value expanding to
  an empty string stops execution.
- Document the 63-level object/array nesting limit on params after expansion.
- Fix the sub-parameters example to use object form (was arrays of objects).
- Interpolate params.where in the Express server example.

Audit keys:
calling/transfer::SWML-TRANSFER-001
calling/transfer::SWML-TRANSFER-002
calling/transfer::SWML-TRANSFER-003
calling/transfer::SWML-TRANSFER-004
calling/transfer::SWML-TRANSFER-006
calling/transfer::SWML-TRANSFER-007
…eferences

- New SleepDuration union (positive integer, -1, or SWML variable reference)
  used by both sleep.duration and the sleep shorthand, so 0 and values below -1
  no longer validate.
- MDX parameter types now include the SWML variable reference string form.

Audit keys:
calling/sleep::SWML-SLEEP-001
calling/sleep::SWML-SLEEP-002
…lling.error events

- Add `to` as an alias for `to_uri`; the schema now requires at least one of
  the two (both allowed), with to_uri taking precedence unless empty.
- Enumerate sip_refer_result values and params.state values, including the
  intermediate inProgress state.
- Qualify event_type and describe the calling.error event that can be sent
  to status_url instead of a calling.call.refer event.

Audit keys:
calling/sip_refer::SWML-SIP_REFER-001
calling/sip_refer::SWML-SIP_REFER-002
calling/sip_refer::SWML-SIP_REFER-003
…ocument answer_result

- codecs accepts a comma-separated string or an array of strings (entries may
  be variable references); valid codecs listed per call type, with the
  not-applied-and-error behavior for invalid preferences.
- username is applied only with a nonempty password after expansion.
- New Variables section for answer_result (success/failed) with its caveats.
- MDX max_duration type and prose cover SWML variable references; new
  YAML/JSON examples for codec arrays and a variable max_duration.

Audit keys:
calling/answer::SWML-ANSWER-001
calling/answer::SWML-ANSWER-002
calling/answer::SWML-ANSWER-003
calling/answer::SWML-ANSWER-004
calling/answer::SWML-ANSWER-005
…plain when reason applies

- reason accepts hangup, cancel, busy, noAnswer, decline, error, or a SWML
  variable reference.
- TypeSpec and MDX explain that reason only affects unanswered inbound calls
  (busy vs declined); answered calls end normally and unanswered outbound
  calls are canceled.
- MDX reason is now a ParamField; new Variables section for hangup_result;
  the reason example no longer answers first.

Audit keys:
calling/hangup::SWML-HANGUP-001
calling/hangup::SWML-HANGUP-002
calling/hangup::SWML-HANGUP-003
calling/hangup::SWML-HANGUP-004
…paths, document calling.error

- status_url accepts a SWML variable reference (existing SWMLVar scalar);
  the resolved value must still be an HTTP(S) URL.
- Callback fields use full params.fax.type / params.fax.params.* paths with a
  params.fax.params object entry, for finished and page events.
- Scope event_type to calling.call.fax and add an Error events section for
  calling.error (params.error.code, params.error.message, diagnostic
  params.request).
- Replace the invented API download route in the example document URL with
  a neutral illustrative URL.

Audit keys:
calling/receive_fax::SWML-RECEIVE_FAX-001
calling/receive_fax::SWML-RECEIVE_FAX-002
calling/receive_fax::SWML-RECEIVE_FAX-003
calling/receive_fax::SWML-RECEIVE_FAX-004
…callback paths and calling.error

- header_info falls back to the call's destination address and identity to
  the originating address (number for phone calls); removed the universal
  caller-ID default badge.
- Note that the method attempts to answer a live, unanswered inbound call.
- Qualify output variables: strings, check send_fax_result first, details may
  be unset on failure.
- Callback fields use full params.fax.* paths; event_type scoped to
  calling.call.fax with a new Error events section for calling.error.

Audit keys:
calling/send_fax::SWML-SEND_FAX-001
calling/send_fax::SWML-SEND_FAX-002
calling/send_fax::SWML-SEND_FAX-003
calling/send_fax::SWML-SEND_FAX-004
calling/send_fax::SWML-SEND_FAX-005
calling/send_fax::SWML-SEND_FAX-006
…-answer and result meaning

- digits: lowercase a-d accepted; at least one valid character and at most
  99 characters (including W/w) after variable expansion; longer sequences
  are not sent.
- Note that the method attempts to answer a live, unanswered inbound call.
- Explain what send_digits_result success and failed mean.

Audit keys:
calling/send_digits::SWML-SEND_DIGITS-001
calling/send_digits::SWML-SEND_DIGITS-002
calling/send_digits::SWML-SEND_DIGITS-003
calling/send_digits::SWML-SEND_DIGITS-004
…result variables

- to_number, from_number and the text-only body now have minLength 1, and
  the docs say they must be nonempty after variable expansion (media-branch
  body unchanged).
- Explain what send_sms_result success/failed mean and add the
  send_sms_response_code output variable.

Audit keys:
calling/send_sms::SWML-SEND_SMS-001
calling/send_sms::SWML-SEND_SMS-002
calling/send_sms::SWML-SEND_SMS-003
Withheld: calling/send_sms::SWML-SEND_SMS-004 (legacy schema artifact only;
canonical TypeSpec and calling schema already include status_callback)
… accurate ringtone and callback behavior

- Add loop (default 1, 0 repeats continuously) to the spec and page.
- Add volume, say_voice, say_language, say_gender ParamFields with the
  property > SWML variable > fallback precedence; drop the fixed
  Polly.Salli say_voice default.
- auto_answer: false does not currently prevent answering.
- urls entries may each be a literal or a SWML variable; status_url
  accepts a SWML variable.
- Accept the bong ringtone; align the ringtone list with the 50
  supported names and drop us-old.
- Document play_result, scope calling.call.play to playback
  notifications, and note the separate calling.error event.

Audit keys: calling/play::SWML-PLAY-001, SWML-PLAY-002, SWML-PLAY-003,
SWML-PLAY-004, SWML-PLAY-005, SWML-PLAY-006, SWML-PLAY-007,
SWML-PLAY-008, SWML-PLAY-009, SWML-PLAY-010, SWML-PLAY-011
…rect outputs and callbacks

- Spec: speech_engine enum (Google, Google.V2, Deepgram); say_gender
  male/female; say_voice, say_language, speech_language and status_url
  literal syntax; timeouts and max_digits at least 1 (max_digits capped
  at 100); volume accepts a SWML variable; speech_hints are literal
  strings; play accepts mixed literal/variable arrays.
- Shared play_url: supported ringtone names only, non-empty say: text,
  ring and silence durations of at least 1 second.
- Page: speech activators and initial_timeout clarified; prompt_value
  normalization, new prompt_value_raw, open prompt_result values;
  callbacks cover calling.call.collect and calling.call.play, drop
  params.final and start_of_input/finished result types, note
  calling.error; prompt.play is string or string array only.

Audit keys: calling/prompt::SWML-PROMPT-002, -003, -004, -005, -006,
-007, -008, -009, -010, -011, -012, -013, -014, -015, -016, -017,
-018, -019, -020, -021, -022, -023
…nds, max_length default, accurate beep and callbacks

- direction accepts both; format and direction accept SWML variables.
- input_sensitivity bounded to 0..100 in the schema.
- max_length defaults to 0 (no maximum length).
- beep plays as recording starts and can be in the recording.
- Callback: drop the error recording state, scope calling.call.record,
  note the separate calling.error event, add params.record.audio and
  full params.record.audio.* paths with mp4/both, add
  params.first_frame_time.

Audit keys: calling/record::SWML-RECORD-001, SWML-RECORD-002,
SWML-RECORD-003, SWML-RECORD-004, SWML-RECORD-005, SWML-RECORD-006,
SWML-RECORD-007, SWML-RECORD-008, SWML-RECORD-009, SWML-RECORD-010
…length default and record_call_start, correct beep and callbacks

- control_id: literal IDs exclude {, }, comma, semicolon, apostrophe,
  backslash and ASCII control characters (recording fails to start
  otherwise); fix the stop_record_call name in the description.
- format and direction accept SWML variables; input_sensitivity bounded
  0..100 and documented as ignored in continuous recording.
- max_length defaults to 0 (no maximum length).
- beep plays as recording starts and can be in the recording.
- Document record_call_start; callback drops the error state, scopes
  calling.call.record, notes calling.error, adds params.record.audio
  with full paths, and corrects first_frame_time presence.

Audit keys: calling/record_call::SWML-RECORD_CALL-001, -002, -003,
-004, -005, -006, -007, -008, -009, -010, -011, -012
…stop_record_call_result

Audit keys: calling/stop_record_call::SWML-STOP_RECORD_CALL-001
… accept variables

Explain that start.direction is ordered and how the first and second
directions map to from_lang/to_lang, voices and filters; require at least
one direction; correct the Deepgram vad_silence_ms default (700), the
omitted-voice default, and drop the bogus inject.direction default; note
that debug_level only applies when service debugging is enabled; document
the string summarize form; add false defaults for live_events/ai_summary;
accept SWML variable references for action, direction and its items,
inject.direction, filters and speech_engine.

Audit keys:
- calling/live_translate::SWML-LIVE_TRANSLATE-001
- calling/live_translate::SWML-LIVE_TRANSLATE-002
- calling/live_translate::SWML-LIVE_TRANSLATE-003
- calling/live_translate::SWML-LIVE_TRANSLATE-004
- calling/live_translate::SWML-LIVE_TRANSLATE-005
- calling/live_translate::SWML-LIVE_TRANSLATE-006
- calling/live_translate::SWML-LIVE_TRANSLATE-007
- calling/live_translate::SWML-LIVE_TRANSLATE-008
- calling/live_translate::SWML-LIVE_TRANSLATE-009
…ept variables

Document start.hints recognition vocabulary; correct the Deepgram
vad_silence_ms default (700) and explain that 0 falls back to the engine
default; explain that vad_thresh 0 uses 400 rather than a zero threshold;
document the string summarize form with an example; accept SWML variable
references for start.direction (whole array or items) and speech_engine.

Audit keys:
- calling/live_transcribe::SWML-LIVE_TRANSCRIBE-001
- calling/live_transcribe::SWML-LIVE_TRANSCRIBE-002
- calling/live_transcribe::SWML-LIVE_TRANSCRIBE-003
- calling/live_transcribe::SWML-LIVE_TRANSCRIBE-004
- calling/live_transcribe::SWML-LIVE_TRANSCRIBE-005
…en-US language

Say that transcribe records from the moment it runs until transcribe_stop
or the call ends, and transcribes asynchronously afterwards, instead of
claiming whole-call coverage completed at hangup; state the fixed en-US
language; document the recording (calling.call.transcribe), transcript
result and startup error events sent to status_url, with the recording
event fields; accept a SWML variable reference for status_url.

Audit keys:
- calling/transcribe::SWML-TRANSCRIBE-001
- calling/transcribe::SWML-TRANSCRIBE-002
- calling/transcribe::SWML-TRANSCRIBE-003
- calling/transcribe::SWML-TRANSCRIBE-004
…callback docs

Constrain rtp_ptime to multiples of 10 from 10 to 120 and show its
variable form; state that RTP destinations need a public IPv4 address and
port and replace the private address in the callback example; accept SWML
variable references for direction and codec; show the speak/PCMU defaults
and remove stray tokens; describe tap_ptime/tap_rate as the tapped call's
audio values; use full params.* paths for callback fields and note the
calling.error failure event; mention the answer attempt; document the
generated control_id in TypeSpec.

Audit keys:
- calling/tap::SWML-TAP-001
- calling/tap::SWML-TAP-002
- calling/tap::SWML-TAP-003
- calling/tap::SWML-TAP-004
- calling/tap::SWML-TAP-005
- calling/tap::SWML-TAP-006
- calling/tap::SWML-TAP-007
- calling/tap::SWML-TAP-008
- calling/tap::SWML-TAP-009
- calling/tap::SWML-TAP-010
…answer attempt

Accept SWML variable references for track, status_url and
status_url_method; state that calling.call.stream status events are
always sent with POST regardless of status_url_method; mention that the
method attempts to answer an unanswered inbound call first.

Audit keys:
- calling/stream::SWML-STREAM-001
- calling/stream::SWML-STREAM-002
- calling/stream::SWML-STREAM-003
Clarify that success means the stop request was accepted, not that the
stream has finished.

Audit keys:
- calling/stop_stream::SWML-STOP_STREAM-001
… the real connector/result contract

Customers can now see and validate the pay contract the engine actually runs:
security_code/postal_code take strings, payment_method accepts ach-debit with a
new bank_account_type, prompts use the singular attempt selector and may use a
play array instead of actions, the prompt targets/card types/error types match
what is recognized, payment_connector_url must be HTTPS, say_voice is
documented, and the connector request/response, status callback fields and
result variables (pay_payment_results is a JSON-encoded string, payment_token is
a token-type label, failed result) match deployed behaviour.

Audit keys:
- calling/pay::SWML-PAY-001 security_code string flag
- calling/pay::SWML-PAY-002 postal_code string input
- calling/pay::SWML-PAY-003 payment_method ach-debit
- calling/pay::SWML-PAY-004 bank_account_type
- calling/pay::SWML-PAY-005 prompts[].attempt
- calling/pay::SWML-PAY-006 prompts[].for targets
- calling/pay::SWML-PAY-007 card_type tokens
- calling/pay::SWML-PAY-008 error_type tokens
- calling/pay::SWML-PAY-009 prompts[].play / optional actions
- calling/pay::SWML-PAY-010 say_voice
- calling/pay::SWML-PAY-011 HTTPS payment_connector_url
- calling/pay::SWML-PAY-012 max_attempts semantics
- calling/pay::SWML-PAY-013 timeout minimum
- calling/pay::SWML-PAY-014 charge_amount default
- calling/pay::SWML-PAY-015 connector response charge_id
- calling/pay::SWML-PAY-016 payment_token meaning
- calling/pay::SWML-PAY-017 pay_payment_results JSON string
- calling/pay::SWML-PAY-018 decoded result fields
- calling/pay::SWML-PAY-019 connector_error presence
- calling/pay::SWML-PAY-020 pay_result failed
- calling/pay::SWML-PAY-021 connector parameters
- calling/pay::SWML-PAY-022 connector raw card values
- calling/pay::SWML-PAY-023 progress attempt/bank and ACH connector fields
- calling/pay::SWML-PAY-024 masked progress fields
- calling/pay::SWML-PAY-025 action type descriptions
- calling/pay::SWML-PAY-026 payment-completed/failed prompt semantics
… and status events

Adds the caller-side whisper_url input, lets status_url take a SWML variable,
limits queue_result to the outcomes the method actually returns, explains when
wait_time/entry_position/entry_size are set, and corrects the meaning of the
status event id and the dequeue event.

Audit keys:
- calling/enter_queue::SWML-ENTER_QUEUE-001 whisper_url
- calling/enter_queue::SWML-ENTER_QUEUE-004 status_url variable alternative
- calling/enter_queue::SWML-ENTER_QUEUE-005 queue_result outcomes
- calling/enter_queue::SWML-ENTER_QUEUE-006 result variables availability
- calling/enter_queue::SWML-ENTER_QUEUE-007 params.id is the queue id
- calling/enter_queue::SWML-ENTER_QUEUE-008 dequeue precedes the bridge

Not changed (legacy orphaned schema, no generation route):
- calling/enter_queue::SWML-ENTER_QUEUE-002
- calling/enter_queue::SWML-ENTER_QUEUE-003
…e, name length, and failure reason

Explains that join_room first tries to answer an unanswered inbound call, that
an automatically created room is set to be deleted after its room sessions end,
that the resolved room name can be at most 100 characters, and adds the
join_room_failed_reason variable.

Audit keys:
- calling/join_room::SWML-JOIN_ROOM-001 resolved name length (prose only)
- calling/join_room::SWML-JOIN_ROOM-002 join_room_failed_reason
- calling/join_room::SWML-JOIN_ROOM-003 auto-created room lifecycle
- calling/join_room::SWML-JOIN_ROOM-004 automatic answer attempt
…r messaging transfer

Messaging transfer.dest now accepts any string and the reference lists the
three destination forms (section name, http(s) URL, inline document as a JSON
string). The URL fetch uses `method` (POST by default) instead of always
POST, method is scoped to URL destinations, params defaults to an empty
object and replaces rather than merges the current params, and variable
expansion in dest and params (recursive, keys untouched) is described with
new section and inline examples.

Audit keys:
- messaging/transfer::SWML-TRANSFER-001
- messaging/transfer::SWML-TRANSFER-002
- messaging/transfer::SWML-TRANSFER-003
- messaging/transfer::SWML-TRANSFER-004
…hand, and return_value persistence

Messaging execute now accepts a destination string as shorthand as well as
the object form, and the reference lists the three destination forms
(section name, http(s) URL fetched with POST, inline document as a JSON
string; called documents start at sections.main). return_value is
documented as assigned only on an explicit return (null for return: null)
and kept, not cleared, when a later subroutine completes without return.
Adds shorthand, URL, and inline YAML/JSON examples.

Audit keys:
- messaging/execute::SWML-EXECUTE-001
- messaging/execute::SWML-EXECUTE-002
- messaging/execute::SWML-EXECUTE-003
…unts, and limits

Messaging goto now accepts a label name as string shorthand (literal label,
default max 100), and goto.max accepts an integer or a messaging-specific
%{...} variable reference (new MessagingVariableReference scalar, so the
calling-oriented SWMLVar prose is not reused). max is documented as a
per-label count shared by every goto for the whole run, checked after each
attempt is counted; exhaustion ends the steps array that holds the label
rather than always the whole section; and the 100-step document limit is
independent of max. Adds variable-max and shorthand YAML/JSON examples.

Audit keys:
- messaging/goto::SWML-GOTO-001
- messaging/goto::SWML-GOTO-002
- messaging/goto::SWML-GOTO-003
- messaging/goto::SWML-GOTO-004
- messaging/goto::SWML-GOTO-005
…larify queued vs delivered

Messaging reply now rejects an empty media array when no body is given
(ReplyWithMedia.media minItems 1) and rejects empty string shorthand and
empty inline-switch string outcomes through a shared ReplyText scalar. The
nonblank-after-expansion body rule, the route- and encoding-dependent
length check, and HTTP(S) URLs for media and status_url are documented,
with media fetching and callbacks happening after the reply is queued.
reply_result now describes queuing rather than delivery, reply_message_id
is the most recent successful reply (not cleared by a failure), and the
status callback timing no longer claims the document completes on queue.

Audit keys:
- messaging/reply::SWML-REPLY-001
- messaging/reply::SWML-REPLY-003
- messaging/reply::SWML-REPLY-004
- messaging/reply::SWML-REPLY-005
- messaging/reply::SWML-REPLY-006
- messaging/reply::SWML-REPLY-007
- messaging/reply::SWML-REPLY-008
…y serialization docs

request_response_code is a string, connection failures report timeout
while failed is reserved for returned non-2xx responses, and a missing or
empty url is documented as the fatal exception to soft failures. Parsed
request_response fields are saved only for 2xx JSON-object responses,
response variables keep earlier values when a call doesn't update them, a
string body is sent as a JSON string, and the stored response body is
described as a possibly re-serialized string truncated to 65,536
characters. The messaging overview's request variables are aligned.

Audit keys:
- messaging/request::SWML-REQUEST-001
- messaging/request::SWML-REQUEST-002
- messaging/request::SWML-REQUEST-003
- messaging/request::SWML-REQUEST-004
- messaging/request::SWML-REQUEST-005
- messaging/request::SWML-REQUEST-006
- messaging/request::SWML-REQUEST-007
… after a branch

The messaging switch reference now says the resolved value is converted to a
string, transformed, and matched exactly against literal case keys, that a
missing variable path resolves to an empty string (matched only by an
empty-string key, otherwise default), and that execution normally continues
with the step after switch once the branch completes. The keyword example
gains a following step to show continuation.

Audit keys:
- messaging/switch::SWML-SWITCH-001
- messaging/switch::SWML-SWITCH-002
…e to null

The messaging return example heading no longer claims a null return leaves
return_value unset; an explicit null return from an execute subroutine
overwrites the caller's return_value with null, while a return in main sets
no variable.

Audit keys:
- messaging/return::SWML-RETURN-001
…decs and callback payloads

- Add stop_all_on_reject (boolean or busy/decline/noAnswer list).
- ringback accepts true/false, one URI, or a URI array; default is a standard ringback tone.
- codecs accepts a CSV string or an array; supported codec names listed.
- session_timeout: no zero default, effective 1-15000 range, not inherited in array modes.
- Destinations: no unconditional defaults (inherit top-level), new stream status_url,
  SIP and stream fields documented in every array tab, non-empty arrays,
  queue/stream combination limits, header and caller ID name constraints.
- confirm: switch, user_event, transcribe, transcribe_stop allowed; restriction kept
  inside cond/switch branches via confirm-specific models.
- call_state_url/events limited to phone, SIP and Resource Address destinations;
  call_state_events items accept variables.
- Callbacks: exact end_reason/failed_reason spellings, full call state payload,
  stream event call identifiers, peer availability, connect-failed delivery to the
  leg's call state URL; execute_after_queue no-bridge path runs result.
- Remove engine variable names from SIP credential descriptions.

Audit keys: calling/connect::SWML-CONNECT-001, -002, -003, -004, -005 (partial),
-006, -007, -008, -009, -010, -011 (partial), -012, -013, -014, -015, -016, -017,
-018, -019, -020, -021, -022, -023, -026, -027. SWML-CONNECT-025 withheld (legacy schema).
…it_call_quality, correct callbacks

- Remove the unsupported `result` parameter.
- join_conference_result is `joined` or `failed`, set when the method finishes.
- start_on_enter starts the conference only once min_participants (default 2) is reached;
  add min_participants and emit_call_quality.
- wait_url is requested with GET while waiting for the start; an empty string plays silence.
- region defaults to global; status_callback_event defaults to end when a callback URL is set;
  relay-format notifications are always POST.
- stream is set up only before the conference starts; stream.realtime booleans are not applied
  (removed from the example).
- Variables accepted in enum, callback URL and stream fields.
- Status callbacks: event_channel per event kind, corrected conference-end sample, new statuses
  (announcement-end/fail, participant-call-quality), conditional payload fields and 21 call-quality statistics.
- record-from-start starts recording once two participants have joined.

Audit keys: calling/join_conference::SWML-JOIN_CONFERENCE-001, -002, -003, -004, -005, -006,
-007, -008, -009, -010, -011, -012, -014, -015, -016, -018, -020.
SWML-JOIN_CONFERENCE-013 withheld (legacy schema).
… and callback payloads

- detect_message_end: false finishes on machine classification; true keeps detecting
  until a beep, the greeting end, or the timeout.
- tone and status_url accept variable references.
- detect_result lists `failed` instead of `error`; wait:false leaves a start snapshot
  (detecting / false / "-1") that callbacks don't refresh; detect_ms is a string.
- Callbacks: full params.detect.* paths with the intermediate params object, fax tone
  events (CNG/CED, finished), and calling.error events.
- Timeout descriptions state seconds.

Audit keys: calling/detect_machine::SWML-DETECT_MACHINE-001, -002 (partial), -003 (partial),
-004, -005, -006, -007, -008, -009, -010.
… outcomes

denoise attempts to answer the call before starting noise reduction. denoise_result
'on' means the start was accepted; 'failed' includes starting while already on.

Audit keys: calling/denoise::SWML-DENOISE-001, calling/denoise::SWML-DENOISE-002.
denoise_result is set to 'off' only when the stop succeeds; a failed stop (for example,
noise reduction not on) leaves it unchanged. The example now states it assumes a prior denoise.

Audit keys: calling/stop_denoise::SWML-STOP_DENOISE-001.
- connect: confirm-only cond no longer re-allows `else` beside when/then and
  allows at most one `else` entry, matching the shared cond rules.
- record_call: control_id pattern allows embedded ${...}/%{...} references,
  since the character rule applies to the expanded ID.
- receive_fax/send_fax: params.request on error events is a diagnostic copy of
  the request, not a stable contract (it does echo the method's inputs).
- errors: every calling script warning sets script_error except
  relay_script_voice_unresolved (consistent with the switch no-match note).
- execute: params nested too deep also reports relay_script_nested_too_deep and
  stops the rest of the current section.
- enter_queue: the agent connection waits for the whisper for up to about a
  minute, then proceeds.
- messaging request: request_result is not promised for every malformed input.
- Wording nits: sleep integer wording, play volume description, pay
  max_attempts, vad_silence_ms minimum, live_transcribe hints variable form,
  messaging switch continuation.

Audit keys:
calling/connect::SWML-CONNECT-011
calling/cond::SWML-COND-001
calling/cond::SWML-COND-002
calling/record_call::SWML-RECORD_CALL-006
calling/receive_fax::SWML-RECEIVE_FAX-003
calling/send_fax::SWML-SEND_FAX-003
calling/switch::SWML-SWITCH-003
calling/execute::SWML-EXECUTE-003
calling/enter_queue::SWML-ENTER_QUEUE-001
messaging/request::SWML-REQUEST-007
calling/sleep::SWML-SLEEP-001
calling/play::SWML-PLAY-003
calling/pay::SWML-PAY-012
calling/live_transcribe::SWML-LIVE_TRANSCRIBE-001
calling/live_transcribe::SWML-LIVE_TRANSCRIBE-002
calling/live_translate::SWML-LIVE_TRANSLATE-001
messaging/switch::SWML-SWITCH-002
…nd prompt model

- ai.agent runs a saved AI agent; prompt is required only for inline agents
- ai.voice, post_prompt_auth_user and post_prompt_auth_password added
- hints: recognition hints vs heard-text pattern/replace rules; hint optional
- pronounce.ignore_case defaults to false and takes a literal boolean
- prompt/post_prompt gain model; confidence marked as having no effect
- post_prompt reference lists pom and max_tokens; SWAIG described as an object
- ai_result documented on the ai method

Audit keys: calling/ai::SWML-AI-001, calling/ai::SWML-AI-002,
calling/ai::SWML-AI-003, calling/ai::SWML-AI-004, calling/ai::SWML-AI-005,
calling/ai::SWML-AI-006, calling/ai::SWML-AI-024, calling/ai::SWML-AI-031,
calling/ai::SWML-AI-056, calling/ai::SWML-AI-066, calling/ai::SWML-AI-067
(partial: calling/ai::SWML-AI-007, calling/ai::SWML-AI-069)
…uirements

- startup_hook / hangup_hook named correctly in prose, params and diagram
- MCP headers and resource_vars are literal; resource_vars fills {name} URI placeholders
- check_time reads ai.params.local_tz
- functions need a web_hook_url (own or defaults) or a data_map
- web_hook_auth_pass fallback also applies on SWAIG.defaults
- SWAIG functions page no longer promises per-function return_value

Audit keys: calling/ai::SWML-AI-032, calling/ai::SWML-AI-033,
calling/ai::SWML-AI-050, calling/ai::SWML-AI-051, calling/ai::SWML-AI-056,
calling/ai::SWML-AI-060 (partial: calling/ai::SWML-AI-069)
- numeric/boolean params take literal values (no SWML variable alternative)
- barge_functions defaults to false; tts_number_format defaults to e164
- attention_timeout 0 or 1000-600000; digit_timeout 1-29999;
  end_of_speech_timeout 0-10000; inactivity_timeout 0 or 10000-3600000
- speech_event_timeout defaults to 0 (recognizer chooses)
- hard_stop_time documents colon/seconds forms, warns that unit suffixes
  are not converted, and describes the final-turn stop
- inner_dialog_synced, max_emotion, max_response_tokens, verbose_logs
  marked as having no effect; speech_timeout reserved
- acknowledge_interruptions accepts a positive count; interrupt_on_noise is boolean
- save_conversation, swaig_set_global_data, swaig_allow_settings reworded

Audit keys: calling/ai::SWML-AI-007, calling/ai::SWML-AI-008,
calling/ai::SWML-AI-009, calling/ai::SWML-AI-010, calling/ai::SWML-AI-011,
calling/ai::SWML-AI-012, calling/ai::SWML-AI-013, calling/ai::SWML-AI-014,
calling/ai::SWML-AI-015, calling/ai::SWML-AI-016, calling/ai::SWML-AI-017,
calling/ai::SWML-AI-018, calling/ai::SWML-AI-019, calling/ai::SWML-AI-020,
calling/ai::SWML-AI-021, calling/ai::SWML-AI-053, calling/ai::SWML-AI-054,
calling/ai::SWML-AI-057, calling/ai::SWML-AI-059 (partial: calling/ai::SWML-AI-069 params.direction)
- context-level instructions are `prompt` (or `pom`), not `text`
- contexts gain initial_step, history, valid_steps, valid_contexts, post_prompt
- prompt.steps for single-context flows; step reference added to the prompt page
- steps can carry reset or gather_info (with questions) instead of text/pom
- skip_user_turn no longer promises advancing; skip_to_next_step added
- empty valid_steps documented as removing step navigation, not advancing
- POM sections described by their real shape; POM example no longer mixes text and pom
- POM numbered flags and skip_user_turn take literal booleans

Audit keys: calling/ai::SWML-AI-023, calling/ai::SWML-AI-025,
calling/ai::SWML-AI-026, calling/ai::SWML-AI-027, calling/ai::SWML-AI-028,
calling/ai::SWML-AI-029, calling/ai::SWML-AI-030, calling/ai::SWML-AI-071
(partial: calling/ai::SWML-AI-069 POM and step flags)
…ith the engine

- data_map webhooks: method GET/POST (default GET, body forces POST), string headers,
  form_param, require_args (renamed in MDX) with any-of semantics, output or expressions
  required, first-response traversal documented
- data_map expressions: expr and nomatch-output; string requires pattern
- function result reference/snippet: optional response (string or split object),
  single action or list, post_process
- actions: transfer destination form, settings, user_event; hold step/timeout_step;
  context_switch system_pom/user_pom/full_reset; toggle_functions single name with
  optional active; unset_* take a key or key list; stop_playback_bg is a presence trigger
- function parameters: items is one schema, format and null schema documented,
  parameters.type and number descriptions fixed, schema metadata takes literals
- function/defaults boolean flags and webhook controls take literal values

Audit keys: calling/ai::SWML-AI-034, calling/ai::SWML-AI-035,
calling/ai::SWML-AI-036, calling/ai::SWML-AI-037, calling/ai::SWML-AI-038,
calling/ai::SWML-AI-039, calling/ai::SWML-AI-040, calling/ai::SWML-AI-041,
calling/ai::SWML-AI-043, calling/ai::SWML-AI-045, calling/ai::SWML-AI-046,
calling/ai::SWML-AI-047, calling/ai::SWML-AI-048, calling/ai::SWML-AI-049,
calling/ai::SWML-AI-055, calling/ai::SWML-AI-061, calling/ai::SWML-AI-062,
calling/ai::SWML-AI-063, calling/ai::SWML-AI-064, calling/ai::SWML-AI-065,
calling/ai::SWML-AI-068, calling/ai::SWML-AI-069, calling/ai::SWML-AI-070
…data_map Bedrock actually reads

Bedrock has its own parsers, so its schema now declares its own contract instead of
inheriting the ai one wholesale:

- prompt: Bedrock subset (text/pom, voice_id, temperature 0.7, top_p 0.9); no max_tokens,
  confidence, penalties, model, contexts or steps; text wins over pom; POM examples fixed.
- post_prompt: text or pom only, described as summary instructions gated by post_prompt_url.
- global_data/meta_data: described as function data, not prompt interpolation; prompt
  variable list replaced with a note that the prompt is used as written.
- params: attention_timeout 0 or 1000-60000; inactivity_timeout reminder, not exit, with 0
  allowed; hard_stop_time/hard_stop_prompt final-instruction behavior and default; video
  talking+idle pair and listening fallback; no SWMLVar on timeouts.
- functions: no active, no lifecycle hook variants, summarize_conversation gated by
  post_prompt_url, meta_data_token scoped to the function name, URL-embedded credentials
  only; SWAIG described as an object.
- data_map: BedrockAction subset (SWML object|string + transfer, set/unset global/meta data);
  optional response with its fallback; action object|array; method GET|POST; no require_args;
  error_keys array only; expr alias and nomatch-output; optional pattern; form_param; string
  headers; foreach.max default 10; template variables are args and session_data.*.

Audit keys:
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-001
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-002
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-003
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-004
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-005
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-006
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-007
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-008
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-009
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-010
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-011
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-012
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-013
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-014
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-015
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-016
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-017
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-018
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-019
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-020
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-021
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-022
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-023
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-024
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-025
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-026
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-027
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-028
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-029
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-030
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-031
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-032
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-033
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-035
(SWML-AMAZON_BEDROCK-034 withheld: legacy schema artifact)
…ning and summarize contract

- Sidecar transcription is separate from live_transcribe and can run alongside it; only a
  second sidecar is rejected.
- swaig_set_global_data is documented as not enforced; swaig_allow_settings and the settings
  action no longer promise a model or settings change; back_to_back_functions and
  extensive_data are documented as having no effect; act_on_channel=false is the gate.
- unset_global_data/unset_meta_data take string | string[]; transfer.summarize removed.
- max_iters_per_tick is a cap on model steps; max_history_tokens is a soft budget.
- speech_timeout, vad_silence_ms and vad_thresh state the values used when omitted or 0.
- transcribe_prompt is the default on-demand summary prompt, not a recognizer bias prompt;
  summarize.prompt falls back to it.
- A summarize request (action.summarize) is its own schema alternative that does not need
  lang, and needs an attached sidecar.
- direction must contain both legs (either order, duplicates allowed).
- Function parameter docs list the six property forms and that defaults are schema
  annotations, not filled-in arguments.
- Callbacks: malformed_tool_call error reason; ask_id/triggered_by limited to the callbacks
  that carry them; the tool webhook's whole body is the model's tool result.

Audit keys:
calling/ai_sidecar::SWML-AI_SIDECAR-001
calling/ai_sidecar::SWML-AI_SIDECAR-002
calling/ai_sidecar::SWML-AI_SIDECAR-003
calling/ai_sidecar::SWML-AI_SIDECAR-004
calling/ai_sidecar::SWML-AI_SIDECAR-005
calling/ai_sidecar::SWML-AI_SIDECAR-006
calling/ai_sidecar::SWML-AI_SIDECAR-007
calling/ai_sidecar::SWML-AI_SIDECAR-008
calling/ai_sidecar::SWML-AI_SIDECAR-009
calling/ai_sidecar::SWML-AI_SIDECAR-010
calling/ai_sidecar::SWML-AI_SIDECAR-011
calling/ai_sidecar::SWML-AI_SIDECAR-012
calling/ai_sidecar::SWML-AI_SIDECAR-013
calling/ai_sidecar::SWML-AI_SIDECAR-014
calling/ai_sidecar::SWML-AI_SIDECAR-015
calling/ai_sidecar::SWML-AI_SIDECAR-016
calling/ai_sidecar::SWML-AI_SIDECAR-017
calling/ai_sidecar::SWML-AI_SIDECAR-018
calling/ai_sidecar::SWML-AI_SIDECAR-019
calling/ai_sidecar::SWML-AI_SIDECAR-022
…ding and the sidecar summarize request

- ai: the shared hints description (reused by the REST AI-agent model, which
  accepts string hints only) no longer describes the pattern/replace object;
  that sentence moved to the SWML ai.hints field.
- ai: `agent` docs say not to combine it with `prompt` (the schema rejects it).
- ai: the `expr` form of a data_map expression documents `pattern` as an
  optional fallback tried against the expanded `expr` text.
- ai: startup_hook/hangup_hook MDX says they run when the conversation
  starts/ends, matching the TypeSpec; the Steps heading gets an explicit
  anchor so in-page links don't land on the `*.steps` field.
- amazon_bedrock: text/pom and string/expr precedence restated as "set one,
  not both"; hard_stop_time examples written as strings; the metadata action
  links revert to their previous target so the docs don't promise more about
  where metadata is written; comments corrected.
- ai_sidecar: summarize-request wording; REST calling.ai_sidecar params now
  accepts the summarize request without `lang`, matching the shared docs.

Audit keys:
calling/ai::SWML-AI-001
calling/ai::SWML-AI-005
calling/ai::SWML-AI-025
calling/ai::SWML-AI-028
calling/ai::SWML-AI-039
calling/ai::SWML-AI-050
calling/ai::SWML-AI-062
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-005
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-006
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-010
calling/amazon_bedrock::SWML-AMAZON_BEDROCK-028
calling/ai_sidecar::SWML-AI_SIDECAR-014
Output of `yarn build:specs` for the SWML audit fixes in this branch. The REST
diff is limited to the SWML.* components the REST tree imports, the AI-agent
hints description, and the calling.ai_sidecar request params.
@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

@Devon-White
Devon-White force-pushed the devon/implement-validated-swml-audit-findings branch from 9ef2505 to baf6a49 Compare October 25, 2026 15:26
@Devon-White Devon-White changed the title fix(swml): apply verified SWML method audit findings (410 confirmed: 397 fixed, 4 partial, 2 already fixed, 7 withheld) fix(swml): align the SWML method reference with platform behavior Oct 25, 2026
Devon-White and others added 3 commits October 26, 2026 08:30
…ptions

Follow-up to the SWML reference corrections, after a review against the
deployed behavior.

Corrections:
- join_conference: an invalid min_participants from a variable makes the join
  fail (a value above 100000 without max_participants falls back to 2); the
  blanket "falls back to 2" claim is removed.
- pay: payment_connector_url accepts a SWML variable again (literals must be
  HTTPS); max_attempts outside 1-3 uses the default; the unsupported ringtone
  "no time limit" claim is removed; TTS gender defaults to female.
- connect: from_name accepts text mixed with variables; the 1-70 character rule
  applies after expansion. An empty serial_parallel group is skipped.
- ai_sidecar: POM numbering flags and MCP resources accept variables again,
  without changing ai or amazon_bedrock.
- ai: context post_prompt no longer offers model; valid_steps, reset, data_map
  webhook fallback, settings action and form_param behavior stated precisely;
  transfer action documents the string form and required dest.
- request: separates responses too deep to store from responses that can't be
  parsed. stream: status_url_method has no effect on status callbacks.
- messaging: return in an executed document sets the caller's return_value;
  overview fetch methods, execute/transfer destinations and variable access
  corrected; errors page lists the execute fetch and document errors.
- Calling overview: transcribe and join_conference.wait_url entries corrected.

Descriptions across the changed specs and pages were also reworded to explain
usage in customer terms. Regenerated SWML JSON Schemas and REST OpenAPI.
- connect: document `call:` destinations, the leading `+` on E.164 numbers,
  `sips:`, `wss://` streams, prefix case rules, and the `from` formats (#547)
- ai.languages and Voices and languages: the `:model` suffix needs an engine
  prefix and overrides `languages[].model`; link the voice-unresolved
  warning (#523, #524)
- ai.SWAIG and Errors: say which failures produce SWML error codes and
  where to trace the rest (#524)
- ai and ai.params: link Conversation analytics from the post-prompt and
  debug webhook references (#519)
- ai.SWAIG and the webhook security guide: protect credentials in the SWML
  document with HTTPS, signature checks, and basic auth in the URL (#522)
@Devon-White

Copy link
Copy Markdown
Collaborator Author

SWML issues addressed in b6da4cb

Before writing anything, I checked each SWML issue on the tracker against the platform source (mod_infrastructure 268f330, mod_openai 21281c1, prime-rails 2bb2f88f2). This commit adds what the source confirmed.

#547: connect destinations

  • The call: destination is documented, with a new Connect to a call in progress example. The target call:

    • must be in the same project
    • can't be the call running connect
    • can't be controlled by cXML
    • must be the only destination

    call_state_url and call_state_events aren't allowed with it. When the call can't be bridged, connect_result is failed.

  • Phone numbers need the leading +; 15552345678 is rejected.

  • sips: is accepted.

  • stream: requires wss://; stream:ws:// is rejected.

  • queue:, call: and stream: are case-sensitive. sip: and sips: aren't.

  • from rejects queue:, call: and stream: values.

  • The queue:/stream rules in parallel, serial, serial_parallel, call_state_url and call_state_events now include call:.

  • These changes are in both the TypeSpec and the MDX, and the schemas and OpenAPI are regenerated.

  • transfer_after_bridge was already removed on main by Name the after-queue field what the engine reads, and drop the stale requirement #709. The boolean/string/array form of ringback was already in this PR.

#523: voice string format

  • ai.languages and Voices and languages now say the :model suffix is read only when the string starts with an engine. For example, rachel:eleven_flash_v2_5 is taken whole as the voice ID.
  • In ai, a suffix takes precedence over languages[].model.

#524: links to the Errors page

  • ai.languages, ai.SWAIG, Voices and languages and the Errors page now link to each other.
  • I limited what each link claims to what the source supports:
    • For ai, the only SWML error codes are an invalid ai block and relay_script_voice_unresolved.
    • A SWAIG function that fails during the call, or a TTS error mid-call, produces no code and doesn't set script_error. The pages point to swaig_log in the post-prompt report and to the debug webhook instead.

#519: AI observability

The guide and the payload reference already existed (Conversation analytics, post_prompt_url callback, debug_webhook_url callback). They weren't linked to each other, so this commit adds links to Conversation analytics from the ai post-prompt reference and the ai.params debug webhook reference.

#522: credentials in SWML documents

  • ai.SWAIG has a new Protect the SWML document warning: credentials in web_hook_url or web_hook_auth_password can be read by anyone who can fetch the document.
  • The webhook security guide now says to serve the document over HTTPS, because http:// is accepted but travels unencrypted. To add a password on top of the signature, put basic auth credentials in the URL. SignalWire strips them and sends an Authorization header.
  • Source:
    • Calls: the fetch honours URL basic auth when a password is present, and signs with both X-Signalwire-Signature and X-Signalwire-SHA256-Signature.
    • Messaging: also honours URL basic auth, and signs with SHA-1 only.
    • The guide already covered both signature schemes.

Already resolved

#521 was fixed on main before this PR and can be closed separately. #520 is about the Compatibility API and isn't in scope here.

Deliberately left out

  • ringback object form ({url, volume}). It passes validation, but create_play_inner in swml.c only plays string items, so an object ringback is silently skipped. I didn't document it until engineering fixes this.
  • SWML dial isn't documented because it's experimental.
  • What happens to a parked call: target after the bridge ends isn't documented because the source doesn't settle it.
  • languages[].engine deprecation. The engine still reads this field, so no deprecation notice was added.

Engineering bugs found

  • An object ringback validates but plays nothing (swml.c, create_play_inner).
  • relay_link_intercept_app (relay.c, around line 6470): when the call: target is on another node, a wrong-project or cXML target returns error instead of notFound or busy.

Unverified

  • Production FORCE_HTTPS_REQUESTS. I don't know the deployed value. If it's on, http:// fetches may be upgraded or rejected, and the "http:// is accepted" wording would need changing.
  • Deployed source revisions. I couldn't confirm that the revisions checked above are the ones in production.

Checks

yarn build:specs passes, and yarn fern-md-check reports all 2828 MDX files valid.

Pages to preview

  • /docs/swml/reference/calling/connect
  • /docs/swml/reference/calling/ai/languages
  • /docs/swml/reference/calling/ai/swaig
  • /docs/swml/reference/calling/ai
  • /docs/swml/reference/calling/ai/params
  • /docs/swml/reference/errors
  • /docs/swml/guides/webhook-security
  • /docs/platform/voice/tts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment