> ## Documentation Index
> Fetch the complete documentation index at: https://docs.deploybaseline.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Twilio

> Programmable Messaging, Messaging Services, Verify, Voice, and Conversations.

Stateful Twilio REST emulation with seeded accounts, Auth Tokens, API keys, incoming phone numbers, Programmable Messaging, Messaging Services, Verify, basic Voice calls, Conversations REST resources, signed webhooks, local simulator routes, and an inspector. Every route is exercised through the `twilio` Node SDK in the emulator's test suite, and responses are checked against Twilio's published OpenAPI specs.

## At a glance

| | |
| - | - |
| Package | `@deploybaseline/twilio` |
| Port | `4012` in the default set, `4000` when started alone |
| Config key | `twilio` |
| HTTP operations | 132, browsable at `http://localhost:3999/docs/twilio` while it runs ([API reference](/api-reference.md)) |
| Vendor spec | [twilio/twilio-oai](https://github.com/twilio/twilio-oai) at `d977514` |

```bash CLI theme={null}
npx @deploybaseline/cli start --service twilio
```

```ts Programmatic theme={null}
import { createEmulator } from "@deploybaseline/cli";

const twilio = await createEmulator({ service: "twilio" });
// twilio.url is http://localhost:4000
```

```json baseline.config.json theme={null}
{
  "twilio": {
    "account": {
      "sid": "AC00000000000000000000000000000000",
      "auth_token": "twilio_test_auth_token",
      "friendly_name": "Local Twilio Account"
    },
    "api_keys": [
      {
        "sid": "SK00000000000000000000000000000000",
        "secret": "twilio_test_api_secret",
        "friendly_name": "Local API Key"
      }
    ],
    "phone_numbers": [
      {
        "phone_number": "+15551234567",
        "friendly_name": "Local SMS and Voice Number",
        "sms_url": "http://localhost:3000/api/twilio/sms",
        "voice_url": "http://localhost:3000/api/twilio/voice"
      }
    ],
    "messaging_services": [
      {
        "friendly_name": "Local Messaging Service",
        "phone_numbers": ["+15551234567"]
      }
    ],
    "verify_services": [
      {
        "friendly_name": "Local Verify Service",
        "code": "123456",
        "default_channel": "sms"
      }
    ],
    "conversations": {
      "services": [
        {
          "friendly_name": "Local Conversations"
        }
      ]
    }
  }
}
```

## Start

```bash theme={null}
npx @deploybaseline/cli start --service twilio
```

When Twilio is the only enabled service, it starts on `http://localhost:4000`. When all services are started, it uses `http://localhost:4012`.

The default account, credentials, phone number, and Verify Service:

```text theme={null}
TWILIO_ACCOUNT_SID=AC00000000000000000000000000000000
TWILIO_AUTH_TOKEN=twilio_test_auth_token
TWILIO_API_KEY=SK00000000000000000000000000000000
TWILIO_API_SECRET=twilio_test_api_secret
TWILIO_PHONE_NUMBER=+15551234567
TWILIO_VERIFY_SERVICE_SID=VA00000000000000000000000000000000
TWILIO_TEST_ACCOUNT_SID=AC11111111111111111111111111111111
TWILIO_TEST_AUTH_TOKEN=twilio_test_credentials_token
```

## URL mapping

| Real Twilio URL | Emulator URL |
| - | - |
| `https://api.twilio.com/2010-04-01/...` | `$TWILIO_EMULATOR_URL/2010-04-01/...` |
| `https://messaging.twilio.com/v1/...` | `$TWILIO_EMULATOR_URL/messaging/v1/...` |
| `https://verify.twilio.com/v2/...` | `$TWILIO_EMULATOR_URL/verify/v2/...` |
| `https://conversations.twilio.com/v1/...` | `$TWILIO_EMULATOR_URL/conversations/v1/...` |

The official Node SDK builds absolute Twilio product URLs. In SDK tests, use a custom request client that rewrites those hosts to the emulator prefixes above. Lists on the Messaging, Verify, and Conversations hosts page with Twilio's `meta` block (`next_page_url`), as the SDK expects.

## Auth

HTTP Basic auth accepts the seeded Account SID and Auth Token pair or an API Key SID and API Secret pair. Product-host APIs are exposed under local prefixes such as `/messaging/v1` and `/verify/v2`; the 2010 API lives at `/2010-04-01`.

### Test credentials

Every account also has Twilio's test credentials: a separate account SID and auth token (`TWILIO_TEST_ACCOUNT_SID` and `TWILIO_TEST_AUTH_TOKEN` above). As on Twilio, they can only create messages, calls, and incoming phone numbers under the test account SID. Nothing they create is stored, sent, or listed, and any other request is `403` with error `20008`. Twilio's magic numbers decide each outcome:

| Request | Magic number | Result |
| - | - | - |
| Message or call `From` | `+15005550006` | Created (`queued`) |
| Message or call `From` | `+15005550001` | `21212` invalid number |
| Message `From` | `+15005550008` | `21611` queue full |
| Message `From` | anything else | `21606` not a message-capable number |
| Call `From` | anything else | `21210` not verified for the account |
| Message `To` | `+15005550001`, `0002`, `0003`, `0004`, `0009` | `21211` invalid, `21612` unreachable, `21408` no permission for the region, `21610` unsubscribed, `21614` not a mobile number |
| Call `To` | `+15005550001`, `0002`, `0003`, `0004` | `21217` invalid, `21214` unreachable, `21215` no permission for the region, `21216` blocked |
| Buy `PhoneNumber` | `+15005550006` | Created |
| Buy `PhoneNumber` | `+15005550000`, `+15005550001` | `21422` unavailable, `21421` invalid |
| Buy by `AreaCode` | `533` | `21452` no numbers found; any other area code buys `+15005550006` |

Any other `To` succeeds. Set your own pair with `test_account_sid` and `test_auth_token` under `account` in the seed config.

## Accounts

`GET /2010-04-01/Accounts.json`, `GET` and `POST /2010-04-01/Accounts/{AccountSid}.json`.

## Incoming phone numbers

`GET` and `POST /2010-04-01/Accounts/{AccountSid}/IncomingPhoneNumbers.json`; `GET`, `POST`, and `DELETE .../IncomingPhoneNumbers/{Sid}.json`.

## Messages

`GET` and `POST /2010-04-01/Accounts/{AccountSid}/Messages.json`; `GET`, `POST`, and `DELETE .../Messages/{MessageSid}.json`; `GET .../Messages/{MessageSid}/Media.json` and `.../Media/{MediaSid}.json`.

* `POST /_twilio/simulate/inbound-message` — Simulate inbound message: creates an inbound message and invokes the configured SMS webhook
* `POST /_twilio/simulate/message-status` — Advance message status: sends status callbacks

For inbound SMS, configure a phone number `sms_url`, then call `POST /_twilio/simulate/inbound-message` with `To`, `From`, and `Body`. If the destination number is assigned to a Messaging Service with `inbound_request_url`, the simulator sends the inbound webhook there and includes `MessagingServiceSid`; otherwise it uses the phone number `sms_url`. A text from a Conversations participant's address to its proxy address goes into that conversation instead. For outbound delivery transitions, create a message with `StatusCallback`, then call `POST /_twilio/simulate/message-status` with `MessageSid`, `Status`, and optionally `ErrorCode`.

## Messaging Services

`GET` and `POST /messaging/v1/Services`; `GET`, `POST`, and `DELETE /messaging/v1/Services/{ServiceSid}`; `GET` and `POST .../PhoneNumbers`; `DELETE .../PhoneNumbers/{Sid}`.

A Messaging Service sender is the phone number itself: it is listed and removed under the number's `PN` sid. A number sends for one Messaging Service at a time, so adding it again, or to a second service, is `409` with error `21710`; a seeded `phone_numbers` entry moves the number to the service that lists it.

## Verify

`GET` and `POST /verify/v2/Services`; `GET`, `POST`, and `DELETE /verify/v2/Services/{ServiceSid}`; `POST .../Verifications`; `GET` and `POST .../Verifications/{Sid}` (cancel); `POST .../VerificationCheck`.

* `GET /_twilio/simulate/verification-code` — Fetch the latest local Verify code: by `VerificationSid` or `To`
* `POST /_twilio/simulate/verification-status` — Force a verification state: by `VerificationSid` or `To`

The seeded Verify Service uses code `123456`, so most local OTP tests can start a verification through the normal Verify API and submit that code. Tests that need to read the current local code can use the authenticated helper route:

```bash theme={null}
curl -u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" \
  "http://localhost:4000/_twilio/simulate/verification-code?To=%2B15550002222&ServiceSid=$TWILIO_VERIFY_SERVICE_SID"
```

The helper returns the latest local verification for that phone number, including `verification_sid`, `status`, `attempts`, and `code`. It is local-only test support, not a production Twilio API. The Verify inspector also shows each attempted code.

## Voice

`GET` and `POST /2010-04-01/Accounts/{AccountSid}/Calls.json`; `GET`, `POST`, and `DELETE .../Calls/{CallSid}.json`.

* `POST /_twilio/simulate/inbound-call` — Simulate inbound call: creates an inbound call and invokes the configured voice webhook
* `POST /_twilio/simulate/call-status` — Advance call status

## Conversations

`GET` and `POST /conversations/v1/Conversations` on the account's default service, and the same routes under `/conversations/v1/Services/{ServiceSid}/Conversations`; `GET`, `POST`, and `DELETE .../Conversations/{ConversationSid}` (by sid or unique name); `GET` and `POST .../Participants` and `.../Messages`; `GET`, `POST`, and `DELETE .../Participants/{Sid}` and `.../Messages/{Sid}`.

Every account has a default Conversations service, created the first time it is needed, and `client.conversations.v1.conversations` works in it; its conversations are also visible under the service's own path. A new conversation takes the account's first Messaging Service unless `MessagingServiceSid` is given. Unique names are unique within a service (`409`, error `50353`), a participant identity or SMS address can join a conversation once (`409`, errors `50433` and `50416`), message indexes only grow (a deleted message's index is not reused), and `Order=desc` lists the newest messages first. An SMS address and proxy address pair belongs to one conversation that is not closed: adding it to a second is `409` (`50416`) naming the first.

### Services and defaults

`GET` and `POST /conversations/v1/Services`; `GET /conversations/v1/Services/{ServiceSid}`; `GET` and `POST /conversations/v1/Configuration` (`DefaultChatServiceSid`).

### Conversations webhooks

`GET` and `POST /conversations/v1/Configuration/Webhooks` (the default service's) and `/conversations/v1/Services/{ServiceSid}/Configuration/Webhooks`; `GET` and `POST .../Conversations/{ConversationSid}/Webhooks`; `GET`, `POST`, and `DELETE .../Webhooks/{Sid}`.

Conversations webhooks follow Twilio's rules. A service's `PostWebhookUrl` receives the events named in `Filters` (`onMessageAdded`, `onConversationStateUpdated`, `onParticipantRemoved`, `onDeliveryUpdated`, and the rest), signed with `X-Twilio-Signature` and sent with the service's `Method`. The account-level `/v1/Configuration/Webhooks` reads and writes the default service's settings. A REST action sends events only when the request has `X-Twilio-Webhook-Enabled: true` (`xTwilioWebhookEnabled: "true"` in the SDK), with `Source=API`. Scoped webhooks under `.../Conversations/{Sid}/Webhooks` hear one conversation: a `webhook` target sends its `Configuration.Filters`, a `trigger` target sends `onMessageAdded` when the message has one of its `Configuration.Triggers` as a word, `Configuration.ReplayAfter` replays the messages from that index on, and `studio` targets are stored without running a flow.

### Delivery receipts

`GET .../Conversations/{ConversationSid}/Messages/{MessageSid}/Receipts` and `.../Receipts/{Sid}`.

A message is texted from each SMS or WhatsApp participant's proxy address to the participant (not to its author, and not to participants without a proxy address), and each text gets a delivery receipt (`DY...`, status `sent`). `POST /_twilio/simulate/message-status` on that text moves the receipt to `delivered`, `read`, `failed`, or `undelivered` (`ErrorCode` sets the error), sends `onDeliveryUpdated`, and updates the message's `delivery` summary. `POST /_twilio/simulate/inbound-message` from a participant's address to its proxy address adds the text to that conversation (`Source=SMS`) instead of calling the number's `sms_url`, texts the other SMS participants, and wakes an inactive conversation. Texts run the service's `PreWebhookUrl` first when `Filters` has `onMessageAdd`: a `4xx` or `5xx` answer drops the text, and a JSON answer may change `body`, `author`, and `attributes`. REST actions never reach the pre-event URL, as on Twilio.

### Roles and users

On the default service at `/conversations/v1` and under `/conversations/v1/Services/{ServiceSid}`: `GET` and `POST .../Roles` and `.../Users`; `GET`, `POST`, and `DELETE .../Roles/{Sid}` and `.../Users/{Sid}` (a user by sid or identity); `GET .../Users/{Sid}/Conversations`; `GET`, `POST`, and `DELETE .../Users/{Sid}/Conversations/{ConversationSid}`; `GET .../ParticipantConversations?Identity=` or `?Address=`.

Every service starts with the four roles Conversations inherits from Programmable Chat: `service admin` and `service user` (type `service`), `channel admin` and `channel user` (type `conversation`). New users get `service user` and new participants `channel user`. Custom roles take only the permissions Twilio lists for their type (`400`, error `20001`, otherwise), and an update replaces the whole list. A participant's `RoleSid` must be a conversation role of its service and a user's a service role. A participant with an identity is a user of the service, made the first time it joins. User identities are unique per service (`409`, `50201`). `.../Users/{Sid}/Conversations` reports each conversation's `unread_messages_count` (null until the participant has read something), `last_read_message_index`, and `notification_level`, and deleting one removes the user from that conversation. `onUserAdded` and `onUserUpdated` go to the service's post-event URL.

### Address configurations

`GET` and `POST /conversations/v1/Configuration/Addresses`; `GET`, `POST`, and `DELETE .../Addresses/{Sid}` (by sid or address).

An address configuration (`IG...`, one per address, `409` `20409` for a second) with `AutoCreation.Enabled` makes a text from someone in no conversation start one in its `AutoCreation.ConversationServiceSid` (or the default service). The sender joins with the address as proxy, and a `webhook` or `studio` type becomes the conversation's scoped webhook, which hears `onParticipantAdded` and `onMessageAdded` with `Source=SMS`.

## Seed config

```json theme={null}
{
  "twilio": {
    "account": {
      "sid": "AC00000000000000000000000000000000",
      "auth_token": "twilio_test_auth_token",
      "friendly_name": "Local Twilio Account",
      "test_account_sid": "AC11111111111111111111111111111111",
      "test_auth_token": "twilio_test_credentials_token"
    },
    "api_keys": [
      {
        "sid": "SK00000000000000000000000000000000",
        "secret": "twilio_test_api_secret",
        "friendly_name": "Local API Key"
      }
    ],
    "phone_numbers": [
      {
        "phone_number": "+15551234567",
        "friendly_name": "Local SMS and Voice Number",
        "sms_url": "http://localhost:3000/api/twilio/sms",
        "voice_url": "http://localhost:3000/api/twilio/voice"
      }
    ],
    "messaging_services": [
      {
        "friendly_name": "Local Messaging Service",
        "phone_numbers": ["+15551234567"]
      }
    ],
    "verify_services": [
      {
        "friendly_name": "Local Verify Service",
        "code": "123456",
        "default_channel": "sms"
      }
    ],
    "conversations": {
      "services": [
        {
          "friendly_name": "Local Conversations",
          "webhooks": {
            "post_webhook_url": "http://localhost:3000/api/twilio/conversations",
            "filters": ["onMessageAdded", "onDeliveryUpdated"]
          },
          "users": [
            {
              "identity": "agent",
              "friendly_name": "Support Agent"
            }
          ]
        }
      ],
      "addresses": [
        {
          "address": "+15551234567",
          "auto_creation": {
            "webhook_url": "http://localhost:3000/api/twilio/new-conversation",
            "webhook_filters": ["onMessageAdded"]
          }
        }
      ]
    }
  }
}
```

| Key | Purpose |
| - | - |
| `account` | Account `sid`, `auth_token`, `friendly_name`, and the [test credentials](#test-credentials) pair `test_account_sid` and `test_auth_token` |
| `api_keys` | API keys: `sid`, `secret`, `friendly_name` |
| `phone_numbers` | Incoming phone numbers: `phone_number`, `friendly_name`, `sms_url`, `voice_url` |
| `messaging_services` | Messaging Services: `friendly_name` and the `phone_numbers` that send for them |
| `verify_services` | Verify Services: `friendly_name`, the fixed `code`, `default_channel` |
| `conversations.services` | Conversations services: `friendly_name`, `webhooks` (`post_webhook_url`, `filters`), and `users` (`identity`, `friendly_name`) |
| `conversations.addresses` | Address configurations: `address` and `auto_creation` (`webhook_url`, `webhook_filters`) |

## Inspector

Open `GET /` in the Twilio emulator to inspect messages, Verify attempts, calls, Conversations, phone numbers, services, credentials, and webhook deliveries.

## Limits

No real SMS, MMS, WhatsApp, email, voice, carrier delivery, A2P 10DLC, toll-free verification, real phone number purchasing, exact rate limits, compliance, billing, SendGrid, Studio, Flex, TaskRouter, Video, Sync, Segment, Conversations SDK websocket behavior, or complete TwiML interpreter behavior is implemented. Conversations media, push notifications, and user online state are not modeled, Studio flows do not run, and webhooks are not retried.

## Conformance

| | |
| - | - |
| Routes | 132 supported or partial, 0 planned |
| Route gate | fail |
| Spec gate | fail, 0 allowed ([twilio/twilio-oai](https://github.com/twilio/twilio-oai) at `d977514`) |
| SDKs tested | `twilio@6.2.0` |
| Dual mode | not yet |

See [CONFORMANCE.md](https://github.com/deploybaseline/cli/blob/main/CONFORMANCE.md) for every provider.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.