{"slug":"laml-compatibility-layer","title":"LaML / Compatibility API — Migrate Twilio Apps to SignalWire","tags":["signalwire","laml","twilio-migration","compatibility-api","cxml","twiml"],"agent_summary":"SignalWire's Compatibility API lets Twilio TwiML and REST API apps run on SignalWire with three changes: credentials, base URL, webhook URLs. Covers cXML/LaML response generation, the @signalwire/compatibility-api SDK, supported feature surface, and the 'change the from number' caveat.","trigger_phrases":["migrate Twilio to SignalWire","TwiML compatibility SignalWire","LaML SignalWire","cXML SignalWire","@signalwire/compatibility-api","Twilio Account SID to Project ID","SignalWire LaML.VoiceResponse"],"runnable":true,"markdown":"\n# LaML / Compatibility API\n\nSignalWire's Compatibility API is a drop-in alternative for Twilio TwiML, REST API, and helper-library code. Existing Twilio apps run on SignalWire with three changes — credentials, endpoint, webhook URLs.\n\n`LaML` (SignalWire Markup Language) and `cXML` are SignalWire's XML voice-control format. They're functionally compatible with Twilio's TwiML for the supported feature surface.\n\n## The three migration changes\n\n### 1. Update credentials\n\nReplace Twilio Account SID and Auth Token with SignalWire Project ID and API Token from your SignalWire Dashboard.\n\n| Twilio | SignalWire |\n|---|---|\n| `Account SID` | `Project ID` |\n| `Auth Token` | `API Token` |\n\n### 2. Change the API endpoint\n\nIf you're hitting Twilio's REST API directly, swap the base URL:\n\n| Twilio | SignalWire |\n|---|---|\n| `https://api.twilio.com/...` | `https://your-space.signalwire.com/...` |\n\n`your-space` is the SignalWire Space name in your dashboard URL.\n\nIf you're using the SignalWire Compatibility SDK (`@signalwire/compatibility-api`), the base URL is set automatically — no manual swap needed.\n\n### 3. Update webhook URLs\n\nPoint your phone number webhooks to your SignalWire Space. Existing TwiML/cXML response handlers work without modification — the markup is compatible.\n\n### Critical caveat: change the `from` number\n\nWhen migrating, **replace the `from` number on outbound API calls with a valid SignalWire DID**. A Twilio-owned number sent through SignalWire won't work — the number must live on your SignalWire project.\n\n## Node.js example\n\n```javascript\n// Replace these lines:\nconst twilio = require('twilio');\nconst response = new twilio.twiml.VoiceResponse();\n\n// With:\nconst { RestClient } = require('@signalwire/compatibility-api');\nconst response = new RestClient.LaML.VoiceResponse();\n\n// Now use response like you did before\nresponse.say('Hey, welcome to SignalWire!');\nresponse.dial('+15555551234');\nconsole.log(response.toString());\n```\n\nThe `LaML.VoiceResponse` API matches Twilio's `VoiceResponse` for `say`, `play`, `dial`, `gather`, `record`, `conference`, `enqueue`, `pause`, `redirect`, `hangup`, and the rest of the core voice surface.\n\n## Sending a call (REST)\n\n```javascript\nconst { RestClient } = require('@signalwire/compatibility-api');\n\nconst client = new RestClient(\n  process.env.SIGNALWIRE_PROJECT_ID,\n  process.env.SIGNALWIRE_API_TOKEN,\n  { signalwireSpaceUrl: 'your-space.signalwire.com' }\n);\n\nclient.calls.create({\n  url: 'https://your.api/voice-handler',\n  to:   '+15555550101',\n  from: '+15555550102',   // MUST be a SignalWire DID\n}).then((call) => console.log(call.sid));\n```\n\n## Sending an SMS (REST)\n\n```javascript\nclient.messages.create({\n  to:   '+15555550101',\n  from: '+15555550102',   // MUST be a SignalWire DID\n  body: 'Hello from SignalWire LaML',\n}).then((msg) => console.log(msg.sid));\n```\n\n## Supported feature surface\n\n| Category | Features |\n|---|---|\n| **Voice** | Inbound/outbound calls, call control, conferencing, queues, recordings, transcriptions |\n| **Messaging** | SMS, MMS, status callbacks |\n| **Fax** | Send and receive faxes |\n| **Phone Numbers** | Purchase, configure, manage DIDs |\n| **Applications** | Manage voice and messaging application configurations |\n\n## LaML vs SWML — which to use post-migration?\n\nAfter the migration, you can keep running on LaML indefinitely. But SignalWire's native [SWML](/topic/swml-overview) unlocks:\n\n- Native AI verb ([ai](/topic/swml-ai-verb)) — voice AI agents in JSON, no separate Media Stream setup.\n- Native expression substitution (`%{call.from}` everywhere).\n- JSON/YAML format choice.\n- First-class background recording (`record_call`) and live transcription (`live_transcribe`).\n- SWAIG function calling.\n\nA common path: migrate on LaML first to get to parity quickly, then move flows that need AI or advanced features to SWML one at a time.\n\n## Twilio Media Streams → SignalWire SWML AI\n\nIf you were using Twilio Media Streams to wire an external AI provider, the SignalWire equivalent is the native `ai` verb — no separate WebSocket pipeline. See the [AI verb](/topic/swml-ai-verb) for the full shape, or use the [Python Agents SDK](/topic/signalwire-python-agents-sdk) for programmatic agents.\n\n## Anti-patterns\n\n- Leaving Twilio `from` numbers on outbound calls — sends will fail or be rejected.\n- Mixing LaML and SWML for the same DID — pick one per number. (Different numbers can use different formats.)\n- Hand-coding the base URL with `api.twilio.com` after migration — change the endpoint, or rely on the SDK.\n- Assuming Twilio Studio export works as-is — Studio JSON does not run on SignalWire. Re-build flows in [Call Flow Builder](/topic/signalwire-call-flow-builder) or convert to SWML.\n- Forgetting that webhook signature validation differs — verify with SignalWire's signature header, not Twilio's.\n\n## See also\n\n- [SWML overview](/topic/swml-overview)\n- [SWML AI verb](/topic/swml-ai-verb)\n- [Python Agents SDK](/topic/signalwire-python-agents-sdk)\n- [Call Flow Builder](/topic/signalwire-call-flow-builder)\n","html":"<h1>LaML / Compatibility API</h1>\n<p>SignalWire's Compatibility API is a drop-in alternative for Twilio TwiML, REST API, and helper-library code. Existing Twilio apps run on SignalWire with three changes — credentials, endpoint, webhook URLs.</p>\n<p><code>LaML</code> (SignalWire Markup Language) and <code>cXML</code> are SignalWire's XML voice-control format. They're functionally compatible with Twilio's TwiML for the supported feature surface.</p>\n<h2>The three migration changes</h2>\n<h3>1. Update credentials</h3>\n<p>Replace Twilio Account SID and Auth Token with SignalWire Project ID and API Token from your SignalWire Dashboard.</p>\n<p>| Twilio | SignalWire |\n|---|---|\n| <code>Account SID</code> | <code>Project ID</code> |\n| <code>Auth Token</code> | <code>API Token</code> |</p>\n<h3>2. Change the API endpoint</h3>\n<p>If you're hitting Twilio's REST API directly, swap the base URL:</p>\n<p>| Twilio | SignalWire |\n|---|---|\n| <code>https://api.twilio.com/...</code> | <code>https://your-space.signalwire.com/...</code> |</p>\n<p><code>your-space</code> is the SignalWire Space name in your dashboard URL.</p>\n<p>If you're using the SignalWire Compatibility SDK (<code>@signalwire/compatibility-api</code>), the base URL is set automatically — no manual swap needed.</p>\n<h3>3. Update webhook URLs</h3>\n<p>Point your phone number webhooks to your SignalWire Space. Existing TwiML/cXML response handlers work without modification — the markup is compatible.</p>\n<h3>Critical caveat: change the <code>from</code> number</h3>\n<p>When migrating, <strong>replace the <code>from</code> number on outbound API calls with a valid SignalWire DID</strong>. A Twilio-owned number sent through SignalWire won't work — the number must live on your SignalWire project.</p>\n<h2>Node.js example</h2>\n<pre><code class=\"language-javascript\">// Replace these lines:\nconst twilio = require('twilio');\nconst response = new twilio.twiml.VoiceResponse();\n\n// With:\nconst { RestClient } = require('@signalwire/compatibility-api');\nconst response = new RestClient.LaML.VoiceResponse();\n\n// Now use response like you did before\nresponse.say('Hey, welcome to SignalWire!');\nresponse.dial('+15555551234');\nconsole.log(response.toString());\n</code></pre>\n<p>The <code>LaML.VoiceResponse</code> API matches Twilio's <code>VoiceResponse</code> for <code>say</code>, <code>play</code>, <code>dial</code>, <code>gather</code>, <code>record</code>, <code>conference</code>, <code>enqueue</code>, <code>pause</code>, <code>redirect</code>, <code>hangup</code>, and the rest of the core voice surface.</p>\n<h2>Sending a call (REST)</h2>\n<pre><code class=\"language-javascript\">const { RestClient } = require('@signalwire/compatibility-api');\n\nconst client = new RestClient(\n  process.env.SIGNALWIRE_PROJECT_ID,\n  process.env.SIGNALWIRE_API_TOKEN,\n  { signalwireSpaceUrl: 'your-space.signalwire.com' }\n);\n\nclient.calls.create({\n  url: 'https://your.api/voice-handler',\n  to:   '+15555550101',\n  from: '+15555550102',   // MUST be a SignalWire DID\n}).then((call) => console.log(call.sid));\n</code></pre>\n<h2>Sending an SMS (REST)</h2>\n<pre><code class=\"language-javascript\">client.messages.create({\n  to:   '+15555550101',\n  from: '+15555550102',   // MUST be a SignalWire DID\n  body: 'Hello from SignalWire LaML',\n}).then((msg) => console.log(msg.sid));\n</code></pre>\n<h2>Supported feature surface</h2>\n<p>| Category | Features |\n|---|---|\n| <strong>Voice</strong> | Inbound/outbound calls, call control, conferencing, queues, recordings, transcriptions |\n| <strong>Messaging</strong> | SMS, MMS, status callbacks |\n| <strong>Fax</strong> | Send and receive faxes |\n| <strong>Phone Numbers</strong> | Purchase, configure, manage DIDs |\n| <strong>Applications</strong> | Manage voice and messaging application configurations |</p>\n<h2>LaML vs SWML — which to use post-migration?</h2>\n<p>After the migration, you can keep running on LaML indefinitely. But SignalWire's native <a href=\"/topic/swml-overview\">SWML</a> unlocks:</p>\n<ul>\n<li>Native AI verb (<a href=\"/topic/swml-ai-verb\">ai</a>) — voice AI agents in JSON, no separate Media Stream setup.</li>\n<li>Native expression substitution (<code>%{call.from}</code> everywhere).</li>\n<li>JSON/YAML format choice.</li>\n<li>First-class background recording (<code>record_call</code>) and live transcription (<code>live_transcribe</code>).</li>\n<li>SWAIG function calling.</li>\n</ul>\n<p>A common path: migrate on LaML first to get to parity quickly, then move flows that need AI or advanced features to SWML one at a time.</p>\n<h2>Twilio Media Streams → SignalWire SWML AI</h2>\n<p>If you were using Twilio Media Streams to wire an external AI provider, the SignalWire equivalent is the native <code>ai</code> verb — no separate WebSocket pipeline. See the <a href=\"/topic/swml-ai-verb\">AI verb</a> for the full shape, or use the <a href=\"/topic/signalwire-python-agents-sdk\">Python Agents SDK</a> for programmatic agents.</p>\n<h2>Anti-patterns</h2>\n<ul>\n<li>Leaving Twilio <code>from</code> numbers on outbound calls — sends will fail or be rejected.</li>\n<li>Mixing LaML and SWML for the same DID — pick one per number. (Different numbers can use different formats.)</li>\n<li>Hand-coding the base URL with <code>api.twilio.com</code> after migration — change the endpoint, or rely on the SDK.</li>\n<li>Assuming Twilio Studio export works as-is — Studio JSON does not run on SignalWire. Re-build flows in <a href=\"/topic/signalwire-call-flow-builder\">Call Flow Builder</a> or convert to SWML.</li>\n<li>Forgetting that webhook signature validation differs — verify with SignalWire's signature header, not Twilio's.</li>\n</ul>\n<h2>See also</h2>\n<ul>\n<li><a href=\"/topic/swml-overview\">SWML overview</a></li>\n<li><a href=\"/topic/swml-ai-verb\">SWML AI verb</a></li>\n<li><a href=\"/topic/signalwire-python-agents-sdk\">Python Agents SDK</a></li>\n<li><a href=\"/topic/signalwire-call-flow-builder\">Call Flow Builder</a></li>\n</ul>\n"}