SWML — SignalWire Markup Language
SWML is a JSON or YAML script that describes a phone call. Each call execution loads one SWML document and runs the verbs inside it. SWML is the SignalWire equivalent of TwiML — but it is JSON-first, has a built-in ai verb, supports expression substitution natively, and accepts both .json and .yaml.
Document shape
version: 1.0.0
sections:
main:
- answer: {}
- play:
url: "say:Hello there."
- hangup: {}
Equivalent JSON:
{
"version": "1.0.0",
"sections": {
"main": [
{ "answer": {} },
{ "play": { "url": "say:Hello there." } },
{ "hangup": {} }
]
}
}
sections is an object; main is the entry section. Other named sections are reachable via goto, execute, or swml_transfer.
How SWML is served
| Source | When to use | |---|---| | Hosted file (S3, your CDN, a static URL) | Truly static flows | | Web hook returning SWML | Anything dynamic — flow depends on caller, time, CRM lookup | | Call Flow Builder export | No-code visual flow, deployed inside the SignalWire portal | | Python Agents SDK | Programmatic, AI-first agents (auto-generates the SWML) |
SignalWire fetches the SWML each call. The endpoint must return application/json or application/yaml. A web hook can vary the SWML per call by reading the inbound request body.
Verb categories
Call control
| Verb | Notes |
|---|---|
| answer | Answers the inbound call. Optional unless explicit timing matters. |
| hangup | Ends the call. Reason field is for logs. |
| connect | Bridges to a PSTN number, SIP URI, or another endpoint. See connect verb. |
| goto | Jumps to another section or step inside the current document. |
| execute | Fetches and runs a sub-document, returns when done. |
| cond | Conditional branching (if/elif/else). |
Media
| Verb | Notes |
|---|---|
| play | Plays an audio URL, ringtone, silence, or TTS via the say: prefix. |
| prompt | Plays a prompt and gathers DTMF or speech. |
| record | Foreground recording (blocking). Use for voicemail. |
| record_call | Background recording (non-blocking). Use for dashboards. |
| live_transcribe | Real-time transcript streaming to a webhook. |
| tap | Forks audio to another endpoint for live monitoring. |
| denoise | Enables noise suppression mid-call. |
AI
| Verb | Notes |
|---|---|
| ai | Runs an AI agent inside the call — speech in, speech out, SWAIG functions. See ai verb. |
| context_switch | Switches the active AI context mid-call. See context switch. |
Queue / conference
| Verb | Notes |
|---|---|
| enter_queue | Place the caller in a call queue. |
| join_conference | Add the caller to a conference room. |
| join_room | Join a Relay video room. |
Detection
| Verb | Notes |
|---|---|
| detect_machine | Answering-machine detection — branches based on human/voicemail outcome. |
Expression substitution
SWML supports inline expressions in any string field.
| Pattern | Expands to |
|---|---|
| %{call.from} | Caller's E.164 number |
| %{call.to} | The dialed DID |
| %{call.call_id} | Unique call ID |
| %{call.direction} | inbound or outbound |
| %{vars.<key>} | Custom variable set earlier |
| %{record_call_url} | URL of last record_call recording |
| %{record_url} | URL of last record (foreground) recording |
| %{prompt_value} | Speech value from last prompt |
Expressions also accept light JavaScript:
- play:
url: "say:Your area code is %{call.from.slice(2,5)}."
In SWAIG/data-map context the prefix is ${...} (not %{...}). The %{...} form is the SWML in-document substitution prefix; ${...} is the SWAIG/DataMap variable prefix.
Sections, goto, and execute
goto is in-document jumping. execute is a sub-routine — fetches and runs a remote SWML, returns.
version: 1.0.0
sections:
main:
- prompt:
play: "say:Press 1 for sales, 2 for support."
max_digits: 1
terminators: "#"
- cond:
if: "%{prompt_value} == '1'"
then:
- goto:
section: sales
elif: "%{prompt_value} == '2'"
then:
- goto:
section: support
else:
- play:
url: "say:Invalid choice."
- hangup: {}
sales:
- connect:
to: "+15555550101"
support:
- connect:
to: "+15555550102"
Anti-patterns
- Forgetting
version: 1.0.0at the top — script fails validation. - Returning HTML or plain text from your webhook — must be JSON or YAML with the correct content type.
- Calling
gotowith a section name that doesn't exist — call hangs up silently. - Mixing
%{...}(SWML) and${...}(SWAIG/DataMap) prefixes — wrong prefix = no substitution. - Hosting SWML on a slow endpoint — every call pays the round-trip. Aim for sub-200ms.