diff --git a/fern/docs.yml b/fern/docs.yml
index 48c3fd1571..55f4806cec 100644
--- a/fern/docs.yml
+++ b/fern/docs.yml
@@ -237,6 +237,12 @@ redirects:
- source: /docs/platform/ai/prompt-engineering/where-to-apply
destination: /docs/platform/ai/prompt-engineering
+ # The VAPI inbound and outbound guides were merged into one integration guide.
+ - source: /docs/platform/ai/vapi/inbound
+ destination: /docs/platform/ai/vapi
+ - source: /docs/platform/ai/vapi/outbound
+ destination: /docs/platform/ai/vapi
+
# SWML methods reorganized into calling/ and messaging/ subsections.
# The old methods overview lived at /docs/swml/reference, which was
# entirely calling-flavored — point it at the new calling overview.
diff --git a/fern/products/platform/pages/ai/guides/Integrations/vapi.mdx b/fern/products/platform/pages/ai/guides/Integrations/vapi.mdx
new file mode 100644
index 0000000000..6f52503a0b
--- /dev/null
+++ b/fern/products/platform/pages/ai/guides/Integrations/vapi.mdx
@@ -0,0 +1,305 @@
+---
+title: VAPI integration
+slug: /ai/vapi
+description: Connect VAPI AI assistants to SignalWire phone numbers over SIP trunking, for inbound calls to an assistant and outbound calls from it.
+max-toc-depth: 3
+---
+
+[signup]: https://signalwire.com/signup
+[vapi]: https://dashboard.vapi.ai/
+[vapi-sip-guide]: https://docs.vapi.ai/advanced/sip/sip-trunk
+[vapi-test-guide]: https://docs.vapi.ai/advanced/sip/sip-trunk#test-your-sip-trunk
+[sw-firewall-guide]: /docs/platform/allow-signalwire-ips-through-your-firewall
+[resources]: https://my.signalwire.com?page=resources
+[addresses-guide]: /docs/platform/addresses
+[swml-guide]: /docs/swml
+[swml-connect]: /docs/swml/reference/calling/connect
+[outbound-calling]: /docs/platform/voice/outbound-calling
+
+VAPI assistants need phone numbers, and SignalWire supplies them over SIP trunking in both
+directions. Callers dial your SignalWire number and reach the assistant, and the assistant places
+calls that show your SignalWire number as caller ID.
+
+Each direction is its own SIP trunk in VAPI and its own SWML script in SignalWire. Set up the one
+you need, or both. The VAPI side follows VAPI's own [SIP trunk setup guide][vapi-sip-guide] with
+SignalWire-specific values.
+
+## What you'll need
+
+- A SignalWire account with at least one phone number ([sign up here][signup])
+- A [VAPI][vapi] account with API access
+- Your VAPI private API key (found in your VAPI dashboard)
+- For outbound calls, a SIP domain app password from SignalWire Support (requested below)
+
+## Inbound calls to your assistant
+
+VAPI has to accept calls from SignalWire's network, and SignalWire has to know which assistant a
+number belongs to. You create a trunk in VAPI that trusts SignalWire's IP addresses, register your
+number with it, and then route the number to VAPI with a SWML script.
+
+### Create the inbound SIP trunk in VAPI
+
+Run the API call below to create the trunk, making sure to replace `YOUR_VAPI_PRIVATE_KEY` with your actual API key.
+
+Save the `id` from the response - you'll need this credential ID for the next step.
+
+
+These IP addresses are current as of this guide's publication, but SignalWire IPs can change and should be programmatically monitored to avoid any impact on calls. You can gather SignalWire's latest IPs by performing a DIG or nslookup of `sip.signalwire.com`.
+
+See our guide on [allowing SignalWire IPs through firewalls][sw-firewall-guide] for more details.
+
+
+```bash
+curl -X POST "https://api.vapi.ai/credential" \
+ -H "Content-Type: application/json" \
+ -H "Authorization: Bearer YOUR_VAPI_PRIVATE_KEY" \
+ -d '{
+ "provider": "byo-sip-trunk",
+ "name": "SignalWire Inbound Trunk",
+ "gateways": [
+ { "ip": "170.64.128.96", "inboundEnabled": true },
+ { "ip": "198.13.56.186", "inboundEnabled": true },
+ { "ip": "104.248.176.184", "inboundEnabled": true },
+ { "ip": "152.42.144.114", "inboundEnabled": true },
+ { "ip": "104.248.150.114", "inboundEnabled": true },
+ { "ip": "138.68.125.160", "inboundEnabled": true },
+ { "ip": "159.65.244.171", "inboundEnabled": true },
+ { "ip": "167.99.198.84", "inboundEnabled": true },
+ { "ip": "13.245.35.235", "inboundEnabled": true },
+ { "ip": "108.61.169.31", "inboundEnabled": true },
+ { "ip": "137.184.4.155", "inboundEnabled": true },
+ { "ip": "188.166.126.7", "inboundEnabled": true },
+ { "ip": "139.59.34.94", "inboundEnabled": true },
+ { "ip": "165.232.186.228", "inboundEnabled": true },
+ { "ip": "157.175.131.128", "inboundEnabled": true }
+ ]
+ }'
+```
+
+### Register your SignalWire phone number
+
+Now register your SignalWire phone number with VAPI using the credential ID from above:
+
+```bash
+curl -X POST "https://api.vapi.ai/phone-number" \
+ -H "Content-Type: application/json" \
+ -H "Authorization: Bearer YOUR_VAPI_PRIVATE_KEY" \
+ -d '{
+ "provider": "byo-phone-number",
+ "name": "SignalWire Inbound Number",
+ "number": "+15551234567",
+ "numberE164CheckEnabled": true,
+ "credentialId": "YOUR_CREDENTIAL_ID"
+ }'
+```
+
+Make sure to replace:
+- `YOUR_VAPI_PRIVATE_KEY` with your actual API key
+- `+15551234567` with your SignalWire phone number in E.164 format (including the + and country code)
+- `YOUR_CREDENTIAL_ID` with the credential ID from the previous step
+
+### Assign your assistant
+
+In your VAPI dashboard, find the phone number you just registered and assign it to one of your AI
+assistants. Configure the inbound settings only. If you also set up outbound calls below, you
+register the same number a second time against the outbound trunk.
+
+### Create the routing script in SignalWire
+
+In your SignalWire dashboard, go to [**Resources**][resources] → **Add** → **Script** → **SWML Script**. This script will handle all incoming calls by immediately connecting them to VAPI:
+
+```yaml
+version: 1.0.0
+sections:
+ main:
+ - connect:
+ to: 'sip:%{call.to}@YOUR_CREDENTIAL_ID.sip.vapi.ai'
+```
+
+Replace `YOUR_CREDENTIAL_ID` with the credential ID from the VAPI trunk setup above. The `%{call.to}` variable ensures VAPI receives the original dialed number, which helps with call routing and analytics.
+
+
+This SWML script uses the [`connect` method][swml-connect] to bridge the incoming call directly to VAPI's SIP endpoint.
+The call happens in real time with no delays. Learn more about SWML in our [complete guide][swml-guide].
+
+
+### Connect the script to your phone number
+
+Go to **Phone Numbers** in your SignalWire dashboard, find your number, and click **Edit Settings**. Under the voice settings, assign your new SWML script to handle incoming calls.
+
+### Test inbound calls
+
+Call your SignalWire phone number. Your VAPI assistant answers within a few seconds. If it doesn't,
+the [troubleshooting section](#troubleshoot-the-vapi-integration) below covers the common causes.
+
+## Outbound calls from your assistant
+
+For outbound calls the roles reverse. SignalWire publishes a SIP address that VAPI dials, and a SWML
+script bridges each call onto the public telephone network with your SignalWire number as caller
+ID. VAPI authenticates to that SIP address with a password issued by SignalWire Support.
+
+### Create the outbound routing script
+
+In your SignalWire dashboard, go to [**Resources**][resources] → **Add** → **Script** → **SWML Script**. This script will handle outbound calls by connecting them through the PSTN:
+
+```yaml
+version: 1.0.0
+sections:
+ main:
+ - connect:
+ answer_on_bridge: true
+ from: +1A-Number-From-Your-Space-here
+ to: '%{call.to.replace(/^sip:/i, '''').replace(/@.*/, '''')}'
+```
+
+Replace `"+1A-Number-From-Your-Space-here"` with an actual phone number from your SignalWire account.
+This will be the caller ID shown to people who receive calls from your VAPI assistant.
+
+
+This SWML script uses the [`connect` method][swml-connect] with `answer_on_bridge: true` to ensure calls connect properly.
+The `to` field uses a regular expression to extract the phone number from VAPI's SIP format and route it through the PSTN.
+
+
+### Add a SIP address to your script
+
+After saving your SWML script, you'll need to create a SIP address that VAPI can connect to:
+
+1. In your saved SWML script, navigate to the **Addresses & Phone Numbers** section
+2. Click **Add** and select **SIP Address**
+3. Configure the SIP address settings and save
+
+After configuration, note down your unique SIP domain app. It will look something like: `test-space-vapi.dapp.signalwire.com`
+
+Learn more about addresses in our [Call Fabric Addresses documentation][addresses-guide].
+
+
+To complete this setup, you must contact SignalWire Support to generate a password for your SIP domain app. VAPI needs this password for authentication.
+
+Contact support by:
+- Clicking the "Help?" button in your SignalWire Space
+- Emailing support@signalwire.com
+
+Provide your SIP domain app and let them know you need a password for VAPI integration.
+
+
+### Create the outbound SIP trunk in VAPI
+
+Run this API call to create the outbound trunk, making sure to replace the placeholder values with your actual configuration:
+
+```bash
+curl -X POST "https://api.vapi.ai/credential" \
+ -H "Content-Type: application/json" \
+ -H "Authorization: Bearer YOUR_VAPI_PRIVATE_KEY" \
+ -d '{
+ "provider": "byo-sip-trunk",
+ "name": "SignalWire Outbound Trunk",
+ "gateways": [{
+ "ip": "YOUR_SIGNALWIRE_SIP_DOMAIN",
+ "inboundEnabled": false
+ }],
+ "outboundLeadingPlusEnabled": true,
+ "outboundAuthenticationPlan": {
+ "authUsername": "YOUR_SIGNALWIRE_PHONE_NUMBER",
+ "authPassword": "YOUR_SIGNALWIRE_PASSWORD"
+ }
+ }'
+```
+
+Make sure to replace:
+- `YOUR_VAPI_PRIVATE_KEY` - Your VAPI API key
+- `YOUR_SIGNALWIRE_SIP_DOMAIN` - Your SIP domain app from the previous step (e.g., `test-space-vapi.dapp.signalwire.com`)
+- `YOUR_SIGNALWIRE_PHONE_NUMBER` - Your SignalWire phone number in E.164 format (e.g., `+15551234567`)
+- `YOUR_SIGNALWIRE_PASSWORD` - The password provided by SignalWire Support
+
+Save the `id` from the response - you'll need this credential ID for the next step.
+
+### Register your SignalWire phone number for outbound
+
+Register your SignalWire phone number with VAPI for outbound calling using the credential ID from above:
+
+```bash
+curl -X POST "https://api.vapi.ai/phone-number" \
+ -H "Content-Type: application/json" \
+ -H "Authorization: Bearer YOUR_VAPI_PRIVATE_KEY" \
+ -d '{
+ "provider": "byo-phone-number",
+ "name": "SignalWire Outbound Number",
+ "number": "+15551234567",
+ "numberE164CheckEnabled": true,
+ "credentialId": "YOUR_CREDENTIAL_ID"
+ }'
+```
+
+Replace:
+- `YOUR_VAPI_PRIVATE_KEY` - Your VAPI API key
+- `+15551234567` - Your actual SignalWire phone number in E.164 format
+- `YOUR_CREDENTIAL_ID` - The credential ID from the trunk creation step
+
+### Assign your assistant for outbound calls
+
+In your VAPI dashboard, find the phone number you just registered and assign it to one of your AI assistants. Configure the outbound settings to specify which assistant should handle outbound calls.
+
+
+If you've already set up inbound calling with the same number, VAPI will indicate this is a duplicate number. This is completely normal and expected - you're using the same phone number for both inbound and outbound calling with different configurations.
+
+
+### Test outbound calls
+
+Your VAPI assistant can now make outbound calls through SignalWire. The calls will show your SignalWire phone number as the caller ID.
+
+To test your setup, refer to VAPI's official documentation on [testing your SIP trunk][vapi-test-guide] for specific instructions on initiating outbound calls.
+
+## Troubleshoot the VAPI integration
+
+You can monitor call logs in both platforms. SignalWire shows the call routing, and VAPI shows the
+assistant interaction details.
+
+### Inbound calls aren't reaching VAPI
+
+Check that your SWML script has the correct credential ID and is assigned to your phone number.
+Also, verify that your phone number is set to accept voice calls. Phone numbers must be in E.164
+format (`+1234567890`) with no spaces or special characters.
+
+### VAPI can't connect on inbound calls
+
+Make sure all SignalWire IP addresses are in your VAPI inbound trunk. If SignalWire has added new
+IP addresses since you created the trunk, you'll need to update it.
+
+### Outbound calls from VAPI fail to authenticate
+
+Double-check that you're using the correct SIP domain app password from SignalWire Support. The
+authentication username should be your phone number in E.164 format.
+
+### Outbound calls from VAPI don't connect
+
+Verify your outbound SWML script has the correct caller ID number and that it's a valid number from
+your SignalWire account. Ensure your SIP domain app is properly configured in SignalWire and that
+VAPI can resolve the domain name.
+
+## Next steps
+
+
+
+ Place outbound calls from SignalWire directly, including calls handled by a SignalWire AI agent
+
+
+
+ Add IVR menus, call screening, or business hours logic before connecting to VAPI
+
+
+
+ Every option on the method both routing scripts use
+
+
diff --git a/fern/products/platform/pages/ai/guides/Integrations/vapi/inbound-calls.mdx b/fern/products/platform/pages/ai/guides/Integrations/vapi/inbound-calls.mdx
deleted file mode 100644
index 7752d50f83..0000000000
--- a/fern/products/platform/pages/ai/guides/Integrations/vapi/inbound-calls.mdx
+++ /dev/null
@@ -1,194 +0,0 @@
----
-id: 85a7837b-5b92-4673-afe5-192995b0fbfb
-title: VAPI inbound calling
-subtitle: Route incoming calls to your VAPI AI assistants using SignalWire's phone network
-slug: /ai/vapi/inbound
-description: Route incoming calls from SignalWire phone numbers to VAPI AI assistants using SIP trunking
----
-
-
-[signup]: https://signalwire.com/signup
-[vapi]: https://dashboard.vapi.ai/
-[vapi-sip-guide]: https://docs.vapi.ai/advanced/sip/sip-trunk
-[sw-firewall-guide]: /docs/platform/allow-signalwire-ips-through-your-firewall
-[resources]: https://my.signalwire.com?page=resources
-[swml-guide]: /docs/swml/
-
-VAPI provides powerful AI voice assistants, but these assistants need phone numbers to receive calls. SignalWire's phone network can route incoming calls directly to your VAPI assistants using SIP trunking. This guide shows you how to connect them.
-
-When you're finished, callers will dial your SignalWire phone number and immediately connect to your VAPI AI assistant - no additional routing or servers needed.
-
-## Setup overview
-
-
-
-### Configure VAPI for SignalWire
-
-Set up a SIP trunk with SignalWire's IP addresses and register your phone number.
-
-### Create call routing in SignalWire
-
-Build a SWML script that forwards incoming calls to your VAPI assistant.
-
-### Connect and test
-
-Assign the routing script to your phone number and verify calls reach your AI assistant.
-
-
-
-## What you'll need
-
-- A SignalWire account with at least one phone number ([sign up here][signup])
-- A [VAPI][vapi] account with API access
-- Your VAPI private API key (found in your VAPI dashboard)
-
-## Setting up VAPI to receive SignalWire calls
-
-VAPI needs to know that calls will be coming from SignalWire's network. We'll create a SIP trunk that includes all of SignalWire's IP addresses, then register your phone number with that trunk.
-
-This follows VAPI's official [SIP trunk setup guide][vapi-sip-guide], but with SignalWire-specific configuration.
-
-### Create the SIP trunk
-
-Run the API call below to create the trunk, making sure to replace `YOUR_VAPI_PRIVATE_KEY` with your actual API key.
-
-Save the `id` from the response - you'll need this credential ID for the next step.
-
-
-These IP addresses are current as of this guide's publication, but SignalWire IPs can change and should be programmatically monitored to avoid any impact on calls. You can gather SignalWire's latest IPs by performing a DIG or nslookup of `sip.signalwire.com`.
-
-See our guide on [allowing SignalWire IPs through firewalls][sw-firewall-guide] for more details.
-
-
-```bash
-curl -X POST "https://api.vapi.ai/credential" \
- -H "Content-Type: application/json" \
- -H "Authorization: Bearer YOUR_VAPI_PRIVATE_KEY" \
- -d '{
- "provider": "byo-sip-trunk",
- "name": "SignalWire Inbound Trunk",
- "gateways": [
- { "ip": "170.64.128.96", "inboundEnabled": true },
- { "ip": "198.13.56.186", "inboundEnabled": true },
- { "ip": "104.248.176.184", "inboundEnabled": true },
- { "ip": "152.42.144.114", "inboundEnabled": true },
- { "ip": "104.248.150.114", "inboundEnabled": true },
- { "ip": "138.68.125.160", "inboundEnabled": true },
- { "ip": "159.65.244.171", "inboundEnabled": true },
- { "ip": "167.99.198.84", "inboundEnabled": true },
- { "ip": "13.245.35.235", "inboundEnabled": true },
- { "ip": "108.61.169.31", "inboundEnabled": true },
- { "ip": "137.184.4.155", "inboundEnabled": true },
- { "ip": "188.166.126.7", "inboundEnabled": true },
- { "ip": "139.59.34.94", "inboundEnabled": true },
- { "ip": "165.232.186.228", "inboundEnabled": true },
- { "ip": "157.175.131.128", "inboundEnabled": true }
- ]
- }'
-```
-
-
-### Register your SignalWire phone number
-
-Now register your SignalWire phone number with VAPI using the credential ID from above:
-
-```bash
-curl -X POST "https://api.vapi.ai/phone-number" \
- -H "Content-Type: application/json" \
- -H "Authorization: Bearer YOUR_VAPI_PRIVATE_KEY" \
- -d '{
- "provider": "byo-phone-number",
- "name": "SignalWire Inbound Number",
- "number": "+15551234567",
- "numberE164CheckEnabled": true,
- "credentialId": "YOUR_CREDENTIAL_ID"
- }'
-```
-
-Make sure to replace:
-- `YOUR_VAPI_PRIVATE_KEY` with your actual API key
-- `+15551234567` with your SignalWire phone number in E.164 format (including the + and country code)
-- `YOUR_CREDENTIAL_ID` with the credential ID from the previous step
-
-### Assign your AI assistant
-
-In your VAPI dashboard, find the phone number you just registered and assign it to one of your AI assistants. You'll only need to configure the inbound settings - we're not using this number for outbound calls.
-
----
-
-## Configuring SignalWire to route calls
-
-Now we need to tell SignalWire where to send incoming calls. We'll create a simple SWML script that forwards calls to your VAPI assistant.
-
-### Create the routing script
-
-In your SignalWire dashboard, go to [**Resources**][resources] → **Add** → **Script** → **SWML Script**. This script will handle all incoming calls by immediately connecting them to VAPI:
-
-```yaml
-version: 1.0.0
-sections:
- main:
- - connect:
- to: 'sip:%{call.to}@YOUR_CREDENTIAL_ID.sip.vapi.ai'
-```
-
-Replace `YOUR_CREDENTIAL_ID` with the credential ID from the VAPI trunk setup above. The `%{call.to}` variable ensures VAPI receives the original dialed number, which helps with call routing and analytics.
-
-
-This SWML script uses the `connect` method to bridge the incoming call directly to VAPI's SIP endpoint.
-The call happens in real time with no delays. Learn more about SWML in our [complete guide][swml-guide].
-
-
-### Connect the script to your phone number
-
-Go to **Phone Numbers** in your SignalWire dashboard, find your number, and click **Edit Settings**. Under the voice settings, assign your new SWML script to handle incoming calls.
-
-That's it - your phone number is now connected to your VAPI assistant.
-
----
-
-## Testing and troubleshooting
-
-Call your SignalWire phone number and you should hear your VAPI assistant answer within a few seconds.
-
-If something isn't working, here are the most common issues:
-
-**Calls aren't reaching VAPI**: Check that your SWML script has the correct credential ID and is assigned to your phone number. Also, verify that your phone number is set to accept voice calls.
-
-**VAPI can't connect**: Make sure all SignalWire IP addresses are in your VAPI trunk. If SignalWire has added new IP addresses since you created the trunk, you'll need to update it.
-
-**Wrong number format errors**: Phone numbers must be in E.164 format (`+1234567890`) with no spaces or special characters.
-
-You can monitor call logs in both platforms - SignalWire shows the initial call routing, while VAPI shows the assistant interaction details.
-
----
-
-## What's next
-
-Now that you have basic inbound routing working, you might want to explore:
-
-
-
- Let your VAPI assistant make outbound calls through SignalWire
-
-
-
- Add IVR menus, call screening, or business hours logic before connecting to VAPI
-
-
-
- Best practices for creating effective VAPI assistants
-
-
diff --git a/fern/products/platform/pages/ai/guides/Integrations/vapi/outbound-calls.mdx b/fern/products/platform/pages/ai/guides/Integrations/vapi/outbound-calls.mdx
deleted file mode 100644
index bce5eb560c..0000000000
--- a/fern/products/platform/pages/ai/guides/Integrations/vapi/outbound-calls.mdx
+++ /dev/null
@@ -1,212 +0,0 @@
----
-id: ee4080eb-4dce-457f-a2cc-b21537700230
-title: Outbound calling
-subtitle: Let your VAPI AI assistants initiate calls through SignalWire's phone network
-slug: /ai/vapi/outbound
-description: Configure VAPI AI assistants to make outbound calls through SignalWire's infrastructure
----
-
-
-[signup]: https://signalwire.com/signup
-[vapi]: https://dashboard.vapi.ai/
-[addresses-guide]: /docs/platform/addresses
-[vapi-sip-guide]: https://docs.vapi.ai/advanced/sip/sip-trunk
-[resources]: https://my.signalwire.com?page=resources
-[vapi-test-guide]: https://docs.vapi.ai/advanced/sip/sip-trunk#test-your-sip-trunk
-
-While VAPI provides powerful AI voice assistants, they need a way to make outbound calls to your customers or contacts.
-SignalWire's phone network can handle outbound calls from your VAPI assistants using SIP trunking. This guide shows you how to connect them for outbound calling.
-
-When you're finished, your VAPI assistants will be able to initiate calls using your SignalWire phone numbers, giving you complete control
-over both inbound and outbound AI-powered communications.
-
-## Setup overview
-
-
-
-### Configure SignalWire for outbound routing
-
-Create a SWML script and SIP domain to handle outbound calls from VAPI.
-
-### Set up VAPI outbound trunk
-
-Configure VAPI with your SignalWire SIP domain and authentication details.
-
-### Connect and test
-
-Register your phone number with VAPI and verify outbound calling works.
-
-
-
-## What you'll need
-
-- A SignalWire account with at least one phone number ([sign up here][signup])
-- A [VAPI][vapi] account with API access
-- Your VAPI private API key (found in your VAPI dashboard)
-- Access to SignalWire support for SIP domain app password generation
-
-## Setting up SignalWire for outbound calls
-
-SignalWire needs to be configured to receive and route outbound call requests from VAPI. This involves creating a SWML script that handles
-the call routing and setting up a SIP address.
-
-### Create the outbound routing script
-
-In your SignalWire dashboard, go to [**Resources**][resources] → **Add** → **Script** → **SWML Script**. This script will handle outbound calls by connecting them through the PSTN:
-
-```yaml
-version: 1.0.0
-sections:
- main:
- - connect:
- answer_on_bridge: true
- from: +1A-Number-From-Your-Space-here
- to: '%{call.to.replace(/^sip:/i, '''').replace(/@.*/, '''')}'
-```
-
-Replace `"+1A-Number-From-Your-Space-here"` with an actual phone number from your SignalWire account.
-This will be the caller ID shown to people who receive calls from your VAPI assistant.
-
-
-This SWML script uses the `connect` method with `answer_on_bridge: true` to ensure calls connect properly.
-The `to` field uses a regular expression to extract the phone number from VAPI's SIP format and route it through the PSTN.
-
-
-### Add a SIP address to your script
-
-After saving your SWML script, you'll need to create a SIP address that VAPI can connect to:
-
-1. In your saved SWML script, navigate to the **Addresses & Phone Numbers** section
-2. Click **Add** and select **SIP Address**
-3. Configure the SIP address settings and save
-
-After configuration, note down your unique SIP domain app. It will look something like: `test-space-vapi.dapp.signalwire.com`
-
-Learn more about addresses in our [Call Fabric Addresses documentation][addresses-guide].
-
-
-**Important**: To complete this setup, you must contact SignalWire Support to generate a password for your SIP domain app. VAPI needs this password for authentication.
-
-Contact support by:
-- Clicking the "Help?" button in your SignalWire Space
-- Emailing support@signalwire.com
-
-Provide your SIP domain app and let them know you need a password for VAPI integration.
-
-
----
-
-## Configuring VAPI for outbound calls
-
-Now we'll set up VAPI to make outbound calls through your SignalWire SIP domain app. This follows VAPI's official [SIP trunk setup guide][vapi-sip-guide], but with SignalWire-specific configuration.
-
-### Create the outbound SIP trunk
-
-Run this API call to create the outbound trunk, making sure to replace the placeholder values with your actual configuration:
-
-```bash
-curl -X POST "https://api.vapi.ai/credential" \
- -H "Content-Type: application/json" \
- -H "Authorization: Bearer YOUR_VAPI_PRIVATE_KEY" \
- -d '{
- "provider": "byo-sip-trunk",
- "name": "SignalWire Outbound Trunk",
- "gateways": [{
- "ip": "YOUR_SIGNALWIRE_SIP_DOMAIN",
- "inboundEnabled": false
- }],
- "outboundLeadingPlusEnabled": true,
- "outboundAuthenticationPlan": {
- "authUsername": "YOUR_SIGNALWIRE_PHONE_NUMBER",
- "authPassword": "YOUR_SIGNALWIRE_PASSWORD"
- }
- }'
-```
-
-Make sure to replace:
-- `YOUR_VAPI_PRIVATE_KEY` - Your VAPI API key
-- `YOUR_SIGNALWIRE_SIP_DOMAIN` - Your SIP domain app from the previous step (e.g., `test-space-vapi.dapp.signalwire.com`)
-- `YOUR_SIGNALWIRE_PHONE_NUMBER` - Your SignalWire phone number in E.164 format (e.g., `+15551234567`)
-- `YOUR_SIGNALWIRE_PASSWORD` - The password provided by SignalWire Support
-
-Save the `id` from the response - you'll need this credential ID for the next step.
-
-### Register your SignalWire phone number for outbound
-
-Register your SignalWire phone number with VAPI for outbound calling using the credential ID from above:
-
-```bash
-curl -X POST "https://api.vapi.ai/phone-number" \
- -H "Content-Type: application/json" \
- -H "Authorization: Bearer YOUR_VAPI_PRIVATE_KEY" \
- -d '{
- "provider": "byo-phone-number",
- "name": "SignalWire Outbound Number",
- "number": "+15551234567",
- "numberE164CheckEnabled": true,
- "credentialId": "YOUR_CREDENTIAL_ID"
- }'
-```
-
-Replace:
-- `YOUR_VAPI_PRIVATE_KEY` - Your VAPI API key
-- `+15551234567` - Your actual SignalWire phone number in E.164 format
-- `YOUR_CREDENTIAL_ID` - The credential ID from the trunk creation step
-
-### Assign your AI assistant for outbound calls
-
-In your VAPI dashboard, find the phone number you just registered and assign it to one of your AI assistants. Configure the outbound settings to specify which assistant should handle outbound calls.
-
-
-If you've already set up inbound calling with the same number, VAPI will indicate this is a duplicate number. This is completely normal and expected - you're using the same phone number for both inbound and outbound calling with different configurations.
-
-
----
-
-## Testing your outbound setup
-
-Your VAPI assistant can now make outbound calls through SignalWire. The calls will show your SignalWire phone number as the caller ID.
-
-To test your setup, refer to VAPI's official documentation on [testing your SIP trunk][vapi-test-guide] for specific instructions on initiating outbound calls.
-
----
-
-## Troubleshooting
-
-**Authentication failures**: Double-check that you're using the correct SIP domain app password from SignalWire Support. The authentication username should be your phone number in E.164 format.
-
-**Calls not connecting**: Verify your SWML script has the correct caller ID number and that it's a valid number from your SignalWire account.
-
-**Domain app issues**: Ensure your SIP domain app is properly configured in SignalWire and that VAPI can resolve the domain name.
-
----
-
-## What's next
-
-Now that you have outbound calling configured, you might want to explore:
-
-
-
- Complete your integration by setting up inbound calls from SignalWire to VAPI
-
-
-
- Add call screening, business hours logic, or complex routing before outbound calls
-
-
-
- Best practices for creating effective VAPI assistants for outbound campaigns
-
-
diff --git a/fern/products/platform/pages/calling/voice/outbound-calling.mdx b/fern/products/platform/pages/calling/voice/outbound-calling.mdx
new file mode 100644
index 0000000000..ba1524143e
--- /dev/null
+++ b/fern/products/platform/pages/calling/voice/outbound-calling.mdx
@@ -0,0 +1,343 @@
+---
+title: Outbound calling
+slug: /voice/outbound-calling
+description: Place outbound calls from SignalWire with the Calling API, an AI agent, the Compatibility API, or a Relay server, then confirm the call connected.
+max-toc-depth: 3
+---
+
+[calling-api]: /docs/apis/rest/calls/call-commands
+[compat-create-call]: /docs/compatibility-api/rest/calls/create-a-call
+[compat-status-callback]: /docs/compatibility-api/rest/calls/webhooks/voice-status-callback
+[relay-dial-python]: /docs/server-sdks/reference/python/relay/client/dial
+[relay-dial-ts]: /docs/server-sdks/reference/typescript/relay/client/dial
+[browser-outbound]: /docs/browser-sdk/v4/guides/outbound-calls
+[sip-credentials]: /docs/platform/voice/sip/sip-credentials
+[caller-id]: /docs/platform/voice/how-to-set-caller-id-or-cnam
+[stir-shaken]: /docs/platform/voice/stir-shaken
+[spam-labels]: /docs/platform/voice/resolving-spam-labels
+[trial-mode]: /docs/platform/trial-mode
+[international]: /docs/platform/how-to-enable-international-services
+[api-credentials]: /docs/platform/your-signalwire-api-space
+[phone-numbers]: /docs/platform/phone-numbers
+[error-codes]: /docs/apis/error-codes
+[swml-ai]: /docs/swml/reference/calling/ai
+[swml-recipe]: /docs/swml/guides/make-and-receive-calls
+[swml-webhook-security]: /docs/swml/guides/webhook-security
+[swml-expressions]: /docs/swml/reference/expressions
+[ai-quickstart]: /docs/platform/ai/quickstart
+[ai-best-practices]: /docs/platform/ai/best-practices
+[tcpa]: /docs/platform/compliance/tcpa
+
+An outbound call takes one request: the number to call, the SignalWire number it comes from, and
+what should happen when someone answers. SignalWire places the call and hands control to your
+SWML, cXML, or Relay code.
+
+The examples below all place the same call. Bayview Taxi phones a rider to say their driver is on
+the way, first as a spoken announcement and then as a conversation with an AI agent.
+
+## Before you dial
+
+You need a [phone number][phone-numbers] purchased in your project, or a
+[verified caller ID][caller-id], to call from. Any number you pass as `from` or `caller_id` on a
+call to the public telephone network must be one of those, in E.164 format such as `+15551234567`.
+
+You also need your Project ID and an API token with voice permissions from the
+[API credentials][api-credentials] page of your Dashboard.
+
+Two account settings decide whether the call is allowed at all. A project in
+[trial mode][trial-mode] can only call purchased and verified numbers and can't call
+internationally. Outside trial mode, calls to other countries still need
+[international dialing enabled][international] for your Space.
+
+## Choose how to place the call
+
+| Method | Pick it when | What controls the call |
+|---|---|---|
+| [Calling API][calling-api] | Your code makes one HTTP request per call | SWML fetched from your URL or sent inline |
+| AI agent | The person you call should talk to an agent | The SWML [`ai` method][swml-ai] |
+| [Compatibility API][compat-create-call] | You're porting cXML code from another provider | cXML fetched from your URL |
+| Relay SDK | Your server stays on the call and reacts to events in real time | Your [Python][relay-dial-python] or [TypeScript][relay-dial-ts] code over a WebSocket |
+| [Browser SDK][browser-outbound] | A user clicks to call from a web page | Your browser code |
+| SIP device | A registered SIP phone or PBX dials out through SignalWire | The call handler on its [SIP credential][sip-credentials] |
+
+The first four are shown below. The Browser SDK guide and the SIP credentials page each cover their own outbound flow.
+
+## Place a call with the Calling API
+
+Send a `dial` command to the [Calling API][calling-api]. The request needs `from`, `to`, and one
+of `url` or `swml`. With `url`, SignalWire requests your endpoint when the call is created and runs
+the SWML it returns. With `swml`, you send the document inline as a JSON object.
+
+
+
+```bash
+curl -X POST "https://YOUR_SPACE.signalwire.com/api/calling/calls" \
+ -u "YOUR_PROJECT_ID:YOUR_API_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "command": "dial",
+ "params": {
+ "from": "+15551234567",
+ "to": "+15557654321",
+ "url": "https://example.com/swml/driver-on-the-way",
+ "status_url": "https://example.com/call-status",
+ "status_events": ["answered", "ended"]
+ }
+ }'
+```
+
+
+```bash
+curl -X POST "https://YOUR_SPACE.signalwire.com/api/calling/calls" \
+ -u "YOUR_PROJECT_ID:YOUR_API_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "command": "dial",
+ "params": {
+ "from": "+15551234567",
+ "to": "+15557654321",
+ "status_url": "https://example.com/call-status",
+ "status_events": ["answered", "ended"],
+ "swml": {
+ "version": "1.0.0",
+ "sections": {
+ "main": [
+ { "play": "say:Hi, this is Bayview Taxi. Your driver is on the way and will arrive in about five minutes." }
+ ]
+ }
+ }
+ }
+ }'
+```
+
+
+
+The response returns the new call right away, before anyone answers:
+
+```json
+{
+ "id": "0e9c80d7-a149-4917-892d-420043709f45",
+ "from": "+15551234567",
+ "to": "+15557654321",
+ "direction": "outbound-api",
+ "status": "queued",
+ "created_at": "2026-09-04T15:20:00Z"
+}
+```
+
+Keep the `id`. Every other Calling API command, such as ending or transferring the call, takes it.
+Progress arrives at `status_url` as webhooks for the events you list in `status_events`: `created`,
+`ringing`, `answered`, and `ended`. If you omit `status_events`, you get `ended` only.
+
+Three optional parameters cover most other needs. `caller_id` shows a different number of yours to
+the person you call. `timeout` sets how many seconds to ring, from 1 to 600. `custom_variables`
+attaches up to 20 key-value strings to the call, which your SWML reads as `${envs.}` using
+[SWML expressions][swml-expressions]. The full list is on the [Calling API reference][calling-api].
+
+## Place a call with an AI agent
+
+An AI agent is an outbound call whose SWML runs the [`ai` method][swml-ai]. The rider can ask how
+far away the driver is or say they no longer need the ride, and the agent answers or acts. Use the
+same `dial` command, and either send the agent inline or point `url` at an agent you serve from
+your own server, as in the [AI quickstart][ai-quickstart].
+
+
+
+```bash
+curl -X POST "https://YOUR_SPACE.signalwire.com/api/calling/calls" \
+ -u "YOUR_PROJECT_ID:YOUR_API_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "command": "dial",
+ "params": {
+ "from": "+15551234567",
+ "to": "+15557654321",
+ "status_url": "https://example.com/call-status",
+ "status_events": ["answered", "ended"],
+ "swml": {
+ "version": "1.0.0",
+ "sections": {
+ "main": [
+ {
+ "ai": {
+ "params": {
+ "static_greeting": "Hello, this is an automated assistant calling from Bayview Taxi about your pickup. This call uses an artificial voice.",
+ "static_greeting_no_barge": true
+ },
+ "prompt": {
+ "text": "You are calling to tell the rider their driver is about five minutes away. Answer questions about the pickup. If the rider says they no longer need the ride, or asks not to be contacted again, call cancel_pickup before saying anything else."
+ },
+ "SWAIG": {
+ "functions": [
+ {
+ "function": "cancel_pickup",
+ "description": "Cancel the pickup and record that the rider does not want further calls",
+ "parameters": { "type": "object", "properties": {} },
+ "web_hook_url": "https://example.com/swaig/cancel-pickup"
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+ }'
+```
+
+
+```bash
+curl -X POST "https://YOUR_SPACE.signalwire.com/api/calling/calls" \
+ -u "YOUR_PROJECT_ID:YOUR_API_TOKEN" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "command": "dial",
+ "params": {
+ "from": "+15551234567",
+ "to": "+15557654321",
+ "url": "https://signalwire:YOUR_AGENT_PASSWORD@agent.example.com/",
+ "status_url": "https://example.com/call-status",
+ "status_events": ["answered", "ended"]
+ }
+ }'
+```
+
+
+
+The `static_greeting` plays in full before the agent's first turn, so the AI disclosure is heard
+even if the rider starts talking. The `cancel_pickup` function calls your server, which cancels the
+ride in your dispatch system and records the opt-out before the agent confirms anything aloud.
+
+
+An AI voice counts as an artificial voice under the Telephone Consumer Protection Act (TCPA).
+Check consent, your do-not-call list, and the local calling hours in your code before you send the
+`dial` request, because once the call is placed it has already happened. The [TCPA guide][tcpa] and
+the compliance section of [AI best practices][ai-best-practices] cover each obligation. This is
+technical guidance, not legal advice.
+
+
+## Place a call with the Compatibility API
+
+Code written against cXML uses the Compatibility API's [Create a Call][compat-create-call]
+endpoint. The request is form-encoded, `From` and `To` are required, and `Url` points at the cXML
+document that runs when the call is answered. Call status webhooks go to `StatusCallback`, with the
+`completed` event by default. See the [voice status callback][compat-status-callback] for the
+payload.
+
+```bash
+curl -X POST "https://YOUR_SPACE.signalwire.com/api/laml/2010-04-01/Accounts/YOUR_PROJECT_ID/Calls" \
+ -u "YOUR_PROJECT_ID:YOUR_API_TOKEN" \
+ --data-urlencode "From=+15551234567" \
+ --data-urlencode "To=+15557654321" \
+ --data-urlencode "Url=https://example.com/cxml/driver-on-the-way.xml" \
+ --data-urlencode "StatusCallback=https://example.com/call-status"
+```
+
+If you're writing new code rather than porting, use the Calling API above. The AI agent example
+and the call commands in this guide run on SWML, which the Calling API executes and the
+Compatibility API doesn't.
+
+## Place a call from your server with Relay
+
+Relay keeps your server on a WebSocket for the life of the call, so you place the call and then
+act on it in the same program. The [`dial`][relay-dial-python] method waits for an answer and
+returns a call object you control directly.
+
+```python
+import asyncio
+from signalwire.relay import RelayClient
+
+client = RelayClient(
+ project="YOUR_PROJECT_ID",
+ token="YOUR_API_TOKEN",
+ host="YOUR_SPACE.signalwire.com",
+ contexts=["default"],
+)
+
+async def main():
+ async with client:
+ call = await client.dial(
+ devices=[[{
+ "type": "phone",
+ "params": {
+ "from_number": "+15551234567",
+ "to_number": "+15557654321",
+ "timeout": 30,
+ },
+ }]],
+ )
+ action = await call.play([{
+ "type": "tts",
+ "params": {"text": "Hi, this is Bayview Taxi. Your driver is on the way."},
+ }])
+ await action.wait()
+ await call.hangup()
+
+asyncio.run(main())
+```
+
+`dial` raises `RelayError` if the call fails or nobody answers within `dial_timeout`, which
+defaults to 120 seconds. The [TypeScript client][relay-dial-ts] has the same method.
+
+## Confirm the call connected
+
+With the Calling API, a `200` response with a call `id` means SignalWire accepted the request, not
+that anyone answered. The `answered` event at your `status_url` is the confirmation, followed by
+`ended` when the call finishes. If `ended` arrives without `answered`, the destination didn't pick
+up or rejected the call. With Relay, a returned call object is the confirmation, and a
+`RelayError` is the failure.
+
+The likeliest failure is a `422` before the call is placed, with an error code such as
+`not_purchased_or_verified`. That means the `from` or `caller_id` number isn't purchased in this
+project or verified as a caller ID. The next section covers the other common causes.
+
+## Troubleshoot outbound calls
+
+### The request is rejected before the call is placed
+
+A `422` response carries an [error code][error-codes] that names the problem. `not_purchased_or_verified`
+means the `from` number isn't yours. `not_valid_for_caller_id` means `from` or `caller_id` isn't an
+E.164 number, caller ID string, or SIP URI. `invalid_destination_number` and
+`destination_number_not_supported` point at `to`. `insufficient_balance` means the project can't pay
+for the call. A `dial` request that has neither `url` nor `swml` is also rejected.
+
+### The call ends without being answered
+
+Check the account limits first. A project in [trial mode][trial-mode] can only reach purchased and
+verified numbers. International destinations fail until you [enable international
+dialing][international]. If those are fine, a `timeout` shorter than the destination's ring time
+ends the call early, and a `max_price_per_minute` below the route's price rejects it before it
+rings.
+
+### The person you call sees a spam warning or the wrong caller ID
+
+The displayed number is `from`, or `caller_id` when you set it, and both must be numbers you own.
+Set the caller name with the [Caller ID and CNAM][caller-id] guide. A "Spam likely" label on your
+number is a reputation problem rather than a configuration one. Start with the
+[spam labels][spam-labels] guide and confirm your calls carry [STIR/SHAKEN][stir-shaken]
+attestation.
+
+### The call connects but your SWML or cXML doesn't run
+
+SignalWire requests `url` with `POST` unless you set `url_method`, and the endpoint has to return a
+SWML document. Set `fallback_url` so a failed fetch still gets instructions, and secure the endpoint
+as described in [SWML webhook security][swml-webhook-security]. An inline `swml` value must be a
+JSON object, not a string of escaped JSON.
+
+## Next steps
+
+
+
+ Every `dial` parameter, plus the commands that control a call after it's placed.
+
+
+ Handle the inbound side and build the SWML your outbound calls run.
+
+
+ Prompt design, speech hints, and the compliance controls an outbound agent needs.
+
+
+ Control the number and name the person you call sees.
+
+