# Migrating from Twilio or Telnyx

Contactivity has its own native API (`/v1/projects/{project_id}/...`, documented in `api/README.md`) and, kept apart
from it, competitor-compatible URL layouts under `/compat/{vendor}`. The compatible layouts exist so that code
written for Twilio or Telnyx keeps working after one change: the SDK base URL. New code should use the native API.

| Migrating from | Set the SDK base URL to | Auth |
|---|---|---|
| Twilio REST (`api.twilio.com/2010-04-01/...`) | `https://api.contactivity.io/compat/twilio` | Basic: AccountSid + AuthToken (or API key SID + secret) from the portal |
| Telnyx Call Control v2 (`api.telnyx.com/v2/...`) | `https://api.contactivity.io/compat/telnyx/v2` | `Authorization: Bearer <API key>` |
| Telnyx TeXML REST (`api.telnyx.com/v2/texml/Accounts/...`) | `https://api.contactivity.io/compat/telnyx/v2` | Basic: AccountSid + AuthToken |

Regional hosts work the same way: `https://api[.<edge>].<region>.contactivity.io/compat/twilio`.
Credentials only work in their own region.

The SDK appends its usual paths to that base, so requests look like:

```
POST https://api.contactivity.io/compat/twilio/2010-04-01/Accounts/{AccountSid}/Calls.json
POST https://api.contactivity.io/compat/telnyx/v2/calls/{call_control_id}/actions/answer
GET  https://api.contactivity.io/compat/telnyx/v2/texml/Accounts/{AccountSid}/Calls
```

The unprefixed layouts (`/2010-04-01/...`, `/v2/...`) are not served.

## SDKs

- **Twilio SDKs.** Our SDK `contactivity` (Node and Python)
  defaults to `https://api.contactivity.io/compat/twilio`. With the upstream `twilio` package, send its requests to
  that base (for example with a custom HTTP client that swaps `https://api.twilio.com` for it).
- **Telnyx SDKs.** Our SDK `contactivity-callcontrol` defaults to `https://api.contactivity.io/compat/telnyx/v2`.
  With the upstream `telnyx` package set `baseURL` (Node) or `base_url` (Python) to that URL.

## Links in responses

Twilio-shaped responses carry `uri`, `subresource_uris`, `first_page_uri` and `next_page_uri` **relative to the
SDK base URL**, exactly as Twilio's are relative to `https://api.twilio.com`: for example
`"uri": "/2010-04-01/Accounts/AC.../Calls/CA....json"`. Twilio SDKs follow them by appending them to the base, so
paging works unchanged. A plain HTTP client should do the same: `https://api.contactivity.io/compat/twilio` + `uri`.

Absolute URLs are given in full, under `/compat/twilio`:

| Field | Example |
|---|---|
| Recording `media_url` | `https://api.contactivity.io/compat/twilio/2010-04-01/Accounts/{AccountSid}/Recordings/{RecordingSid}` |
| `RecordingUrl` (recording status callback, `<Record>` action) | same as `media_url` |
| `TranscriptionUrl` (transcribe callback) | `https://api.contactivity.io/compat/twilio/2010-04-01/Accounts/{AccountSid}/Recordings/{RecordingSid}/Transcriptions/{TranscriptionSid}` |

Append `.wav` or `.mp3` to a recording URL to download the audio. Telnyx-shaped events carry native recording
URLs (`https://api.contactivity.io/v1/voice/Recordings/{id}.wav`).

## What is supported

### Twilio (`/compat/twilio/2010-04-01/Accounts/{AccountSid}/...`)

The `AccountSid` in the URL must be the authenticated account (the `AC...` SID, or the project id); another
account's SID is refused with `401`, as Twilio does. Voice resources only:

| Resource | Methods |
|---|---|
| `{AccountSid}.json` (account) | GET |
| `Calls`, `Calls/{CallSid}` | GET, POST (create, modify), DELETE |
| `Calls/{CallSid}/Recordings[/{RecordingSid}]` | GET, POST, DELETE |
| `Calls/{CallSid}/Streams[/{StreamSid}]` | POST |
| `Conferences[/{ConferenceSid}]`, `.../Participants[/{CallSid}]`, `.../Recordings[/{RecordingSid}]` | GET, POST, DELETE |
| `Recordings[/{RecordingSid}]`, `.../Transcriptions[/{TranscriptionSid}]`, `Transcriptions[/{Sid}]` | GET, DELETE |
| `Queues[/{QueueSid}]`, `.../Members[/{CallSid}]` | GET, POST, DELETE |
| `Applications[/{ApplicationSid}]` | GET, POST, DELETE |
| `OutgoingCallerIds[/{Sid}]` | GET, POST, DELETE |
| `IncomingPhoneNumbers[/{Sid}]` | GET, POST, DELETE (see below) |
| `AvailablePhoneNumbers[/{Country}/{Type}]` | GET (see below) |

Parameters, response fields and error codes per resource: [api/compatibility.md](/guides/compatibility.md) and the
[Compatibility](/compatibility/) reference (its `/v1/voice/...` paths are what these URLs reach).

#### Phone numbers

`GET AvailablePhoneNumbers.json` lists the countries numbers are sold in (US only during beta), each with
`subresource_uris` for `local`, `toll_free` and `mobile`.

`GET AvailablePhoneNumbers/{Country}/{Local|TollFree|Mobile}.json` searches numbers to buy:

| Parameter | Meaning |
|---|---|
| `AreaCode` | Area code (for `TollFree`, the toll-free prefix, e.g. `833`) |
| `Contains` | Digits the number contains; `*` only at the start or end |
| `InRegion` | State, e.g. `CA` |
| `PageSize` | 1-1000, default 50 (at most 200 numbers are returned) |

Other Twilio filters (`InPostalCode`, `InLocality`, `InRateCenter`, `InLata`, `NearNumber`, `NearLatLong`,
`Distance`) and `SmsEnabled`/`MmsEnabled`/`FaxEnabled=true` are refused with `400` / `20001` rather than ignored.
Numbers are voice-only during beta (`capabilities.SMS` and `MMS` are `false`).

`POST IncomingPhoneNumbers.json` buys `PhoneNumber`, or the first number found in `AreaCode`, and routes it with the
usual fields (`VoiceUrl`, `VoiceMethod`, `VoiceApplicationSid`, `StatusCallback`, ...). It charges the account like
an order in the portal and honours `Idempotency-Key`. `POST IncomingPhoneNumbers/{Sid}.json` updates the routing;
`DELETE IncomingPhoneNumbers/{Sid}.json` releases the number.
Every other `DELETE` (Calls, Recordings, Transcriptions, Queues, Applications, OutgoingCallerIds) archives: the
record is kept but answers `404` and leaves lists, and it stops working at once. Nothing is erased.

| Code | HTTP | When |
|---|---|---|
| 21421 | 400 | `PhoneNumber` and `AreaCode` both missing, or `PhoneNumber` is not E.164 |
| 21422 | 409 / 400 | The number is not available to buy |
| 21452 | 400 | No number found in `AreaCode` |
| 20402 | 402 | Balance too low (the message gives the amount required and the balance) |
| 21220 | 409 | Release refused: the number is still being provisioned by the carrier |
| 20001 | 400 | `AreaCode` is not 3 digits, or an unsupported search filter |
| 20503 | 503 | Number search is temporarily unavailable |

### Telnyx (`/compat/telnyx/v2/...`)

| Path | Methods |
|---|---|
| `calls`, `calls/{call_control_id}` | POST (dial), GET |
| `calls/{call_control_id}/actions/{command}` | POST (`client_state_update` also PUT) |
| `conferences`, `conferences/{id}`, `conferences/{id}/participants` | GET, POST |
| `conferences/{id}/actions/{command}` | POST |
| `recordings`, `recordings/{id}` | GET, DELETE |
| `call_control_applications[/{id}]`, `texml_applications[/{id}]` | GET, POST, PATCH, DELETE |
| `texml/Accounts/{AccountSid}/...` | The Twilio resources above, with Telnyx TeXML's JSON request bodies accepted |

`DELETE` on recordings and applications archives them: they answer `404` and leave lists afterwards; nothing is erased.

## Unchanged: markup and callbacks

TwiML (CoXML) and Plivo XML documents, Twilio-style webhook parameters (`AccountSid`, `CallSid`, `ApiVersion`, ...)
and `X-Twilio-Signature`, and Telnyx-style webhook events and `telnyx-signature-ed25519` are markup and callback
formats, not routes. They work the same whichever API created the call.

## Known differences

- Only voice is served. Other Twilio product namespaces (Messaging, Verify, ...) and other Telnyx resources return `404`.
- TeXML responses use Twilio's response shape and `uri` values (relative to `/compat/twilio`), not Telnyx's `/v2/texml` URIs.
- Phone numbers: US only and voice-only during beta; the search filters listed above only.
- Error bodies follow each vendor's shape; `more_info` links point to the vendor's error pages.
