# Moving from Twilio

The Calls API follows Twilio's `2010-04-01` Calls and Recordings resources, and OneML is compatible with TwiML, so most Twilio voice integrations run on Signals after you change the base URL and credentials. This page names Twilio and TwiML directly. It lists what you change, what Signals serves, and where behaviour differs.

## What you change

| | Twilio | Signals |
|---|---|---|
| Base URL | `https://api.twilio.com` | `https://api.signals.pressone.africa` |
| Path | `/2010-04-01/Accounts/{AccountSid}/...` | Same |
| Username | Account SID, or an API key SID (`SK...`) | Voice Account `AccountSid` only. It must equal the `AccountSid` in the path. |
| Password | Auth token, or API key secret | Voice Account auth token |
| Webhook signature key | Auth token | Voice Account auth token. `X-Twilio-Signature` is computed as on Twilio. See [Request signatures](/programmable-voice/webhooks#request-signatures). |
| Inbound call setup | Phone number voice URL, or a TwiML App | A voice application, with a number routed to it. See [Receiving calls](/programmable-voice/receiving-calls). |

Configure your HTTP client, or your helper library's HTTP layer, to send requests to `api.signals.pressone.africa`. Find the `AccountSid` and auth token in the Signals console under **Developers**, **Voice Account**.

## Resources Signals serves

| Resource | Methods | Notes |
|----------|---------|-------|
| `Calls.json` | `GET`, `POST` | List and create calls. |
| `Calls/{CallSid}` (`.json` optional) | `GET`, `POST`, `DELETE` | Fetch, update and end a call. `PATCH` is not accepted. |
| `Calls/{CallSid}/Recordings.json` | `GET` | List a call's recordings. |
| `Calls/{CallSid}/Events.json` | `GET` | The requests Signals made to your URLs during the call, with your responses. See [Inspect requests with call events](/programmable-voice/webhooks#inspect-requests-with-call-events). |
| `Recordings/{RecordingSid}` (`.json` optional) | `GET` | Recording metadata. |
| `Recordings/{RecordingSid}.wav` | `GET`, `HEAD` | Recording audio. `.mp3` and other formats are not served. |

Every other Twilio resource, such as `Messages`, `IncomingPhoneNumbers`, `Applications`, `Conferences` or `Queues`, returns `404` with code `voice_route_not_proxied`. Manage numbers and voice applications through the [Signals API](/api/programmable-voice) instead.

## Supported verbs

Each OneML verb links to its reference page.

| OneML verb | TwiML verb | Notes |
|------------|------------|-------|
| [Say](/programmable-voice/oneml/say) | `<Say>` | Voice and language handling differ. See [Behaviour differences](#behaviour-differences). |
| [Play](/programmable-voice/oneml/play) | `<Play>` | |
| [Gather](/programmable-voice/oneml/gather) | `<Gather>` | |
| [Record](/programmable-voice/oneml/record) | `<Record>` | |
| [Dial](/programmable-voice/oneml/dial) | `<Dial>` | Bare numbers and `<Number>` children only. |
| [Redirect](/programmable-voice/oneml/redirect) | `<Redirect>` | |
| [Pause](/programmable-voice/oneml/pause) | `<Pause>` | |
| [Hangup](/programmable-voice/oneml/hangup) | `<Hangup>` | |
| [Reject](/programmable-voice/oneml/reject) | `<Reject>` | |
| [Stream](/programmable-voice/oneml/stream) | `<Connect><Stream>` | Two-way, blocking media session. |
| [Stream](/programmable-voice/oneml/stream) | `<Start><Stream>`, `<Stop><Stream>` | One-way, non-blocking media session. |
| [Disconnect](/programmable-voice/oneml/disconnect) | None | OneML extension. |

## Not supported

The elements and attributes below are not implemented. They fail in different ways: some reject the document, others are skipped, and others are accepted with no effect. A rejected document is visible at once; a skipped element is not, so check your documents for these before you move.

### Rejected with a validation error

| Verb | What happens |
|------|--------------|
| `<Pay>` | Parses as XML but fails validation with `<Pay> is not supported yet` when Signals runs the document, and the call ends as failed. This applies to a webhook response, a `<Redirect>` target and inline `Twiml`. For inline `Twiml` on create-call, the request itself still returns `201 Created`, because the request only checks that the XML is well formed. The failure appears later as `status: failed` on the call resource or in a status callback. |

### Accepted and skipped

These elements pass validation, but nothing runs them. Signals moves on to the next verb with no error, no `action` request and no signal to your server or the caller.

| Element | Context | Notes |
|---------|---------|-------|
| `<Enqueue>` | Child of `<Response>` | |
| `<Leave>` | Child of `<Response>` | |
| `<Refer>` | Child of `<Response>` | Its `sendDigits` format is checked, but the verb never runs. |
| `<Siprec>` | Child of `<Start>` or `<Stop>` | |
| `<Transcription>` | Child of `<Start>` or `<Stop>` | |
| `<ConversationRelay>` | Child of `<Connect>` | Only `<Connect><Stream>` is implemented. |
| `<Room>` | Child of `<Connect>` | |
| `<VirtualAgent>` | Child of `<Connect>` | |

`<Start>` and `<Stop>` still need at least one recognized child (`<Siprec>`, `<Stream>` or `<Transcription>`) to pass validation. An empty `<Start>` or `<Stop>` is rejected. Only the `<Stream>` child runs.

### Dial targets that are dropped

`<Dial>` dials bare phone numbers and `<Number>` children. The following targets parse without error but are dropped when Signals builds the list of numbers to dial. If a `<Dial>` contains only these targets, Signals reports a failed dial to its `action` URL when one is set.

| Target | Notes |
|--------|-------|
| `<Conference>` | |
| `<Client>` | |
| `<Queue>` | |
| `<Application>` | |
| `<Sip>` | Not available for Programmable Voice. To reach your own PBX, dial a Signals number that is routed to your SIP trunk; Signals connects it on net. |

### Dial attributes that are ignored

These `<Dial>` attributes are validated but have no effect.

| Attribute | Notes |
|-----------|-------|
| `trim` | Recording trim is not configurable through `<Dial>`. |
| `referUrl` | |
| `recordingTrack` | |

## REST API differences

- **The Basic username must be the path `AccountSid`.** Signals rejects any other username with `401` before the request goes further. Twilio API key SIDs (`SK...`) and parent-account credentials on a subaccount path are not accepted.
- **SIDs are lowercase.** `AccountSid`, `CallSid` and `RecordingSid` must be two uppercase letters followed by 32 lowercase hexadecimal characters. Any other form returns `404`.
- **`ApplicationSid` is rejected.** Create-call fails with error `21605` when `ApplicationSid` is present. Use `Url` or `Twiml`. A Signals voice application ID is not accepted there either; voice applications serve inbound numbers.
- **Inbound numbers point at voice applications.** A Signals number has no voice URL of its own. Create a voice application and route the number to it.
- **`Timeout` defaults to 60 seconds** on create-call, the same as Twilio.
- **Call updates use form fields.** Send `POST` with `application/x-www-form-urlencoded` and exactly one of `Url`, `Twiml`, or `Status`. `Method` accepts `GET` or `POST` with `Url`. `Status=completed` ends an in-progress call; `Status=canceled` cancels before answer. Success returns HTTP 200 and the call resource. The final status follows the switch event. JSON update clients must adopt the form contract.
- **The recordings list is not paginated.** It returns every recording for the call in one response, with no `Page` or `PageSize` and no page metadata.
- **Recording audio needs authentication.** `RecordingUrl` and the `.wav` resource require your `AccountSid` and auth token. Recording metadata carries no media URL. See [Download recording audio](/programmable-voice/recording-calls#download-recording-audio).
- **Call resources link to their events.** Call responses include `subresource_uris.events`, pointing at `Calls/{CallSid}/Events.json`.
- **Request bodies are limited to 1 MiB**, and Signals waits at most 15 seconds for the voice platform to answer before returning `504`.
- **Error bodies have three shapes.** Errors Signals raises itself (bad credentials format, unknown account or path, timeouts) are `application/problem+json` documents with a `code` such as `unauthenticated` or `voice_route_not_proxied`. Create-call validation errors are Twilio-style JSON (`code`, `message`, `more_info`, `status`). Other voice platform errors are plain text (`Unauthorized` or `Not Found`). Call-update and end-call errors use Twilio-style JSON with the matching HTTP status. List-recordings errors retain the older `401` behaviour.

## Behaviour differences

- **`<Dial>` works only inside an active call.** As with Twilio, `<Dial>` does not start an outbound call. Create the call through the Calls API, then use `<Dial>` in its OneML.
- **`<Dial>` caller IDs must be yours.** The caller ID must be a number your project owns. Without `callerId`, Signals uses `To` for inbound calls and `From` for calls created through the Calls API. The selected number must be eligible and owned by the same tenant. It does not treat an arbitrary incoming caller number as owned. Dialed numbers go through your project's [outbound policy](/sip-trunks/outbound-policies).
- **`<Dial record>` dual-channel modes record one mixed channel.** The from-answer and from-ringing start points are honoured, but `record-from-answer-dual` and `record-from-ringing-dual` do not produce a channel per party.
- **`<Say>` uses Google Cloud Text-to-Speech, not Amazon Polly**, including for the `man`, `woman` and `alice` aliases.
- **`<Say voice>` is not checked against a list.** `man` (the default) and any unrecognized plain string select a Google male voice; `woman` and `alice`, in lowercase, select a Google female voice. See [Voice selection](/programmable-voice/oneml/say#voice-selection).
- **`<Say voice>` accepts a provider prefix.** `Google.<voice-name>` and `Elevenlabs.<voice-id>` select a specific Google Cloud Text-to-Speech or ElevenLabs voice.
- **`<Say language>` must match the voice.** A mismatch returns a clear error. The built-in aliases support `en` and `en-US`. For another Google language, select a matching `Google.<voice-name>`. The current ElevenLabs integration accepts `en` and `en-US`.
- **`<Number sendDigits>` is accepted but not applied.** Signals checks its format but does not send the tones.
- **`<Connect>` `action` and `method` are never requested.** When a `<Connect><Stream>` session ends, the next verb runs.
- **`<Start><Stream>` metadata omits `account_sid`.** `<Connect><Stream>` includes it. See [Stream](/programmable-voice/oneml/stream).
- **Inbound status callbacks come from the voice application.** An inbound call sends a `completed` status callback only when its voice application has a status callback URL. API-created calls use `StatusCallback` as on Twilio. See [Status callbacks](/programmable-voice/status-callbacks).
- **`StatusCallbackEvent` is not validated.** Any string is accepted; only `initiated`, `ringing`, `answered` and `completed` ever match an event.
- **The webhook user agent keeps its Twilio name.** Requests carry `User-Agent: TwilioProxy/1.1`.

## Next steps

- [Quickstart](/programmable-voice/quickstart): place a call with your Signals credentials.
- [Receiving calls](/programmable-voice/receiving-calls): move an inbound number to a voice application.
- [OneML overview](/programmable-voice/oneml/overview): every OneML verb and attribute.
- [Calls API reference](/api/voice-calls): every Calls API endpoint.
