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}/CallsThe unprefixed layouts (/2010-04-01/..., /v2/...) are not served.
SDKs
- Twilio SDKs. Our SDK
contactivity(Node and Python) defaults tohttps://api.contactivity.io/compat/twilio. With the upstreamtwiliopackage, send its requests to that base (for example with a custom HTTP client that swapshttps://api.twilio.comfor it). - Telnyx SDKs. Our SDK
contactivity-callcontroldefaults tohttps://api.contactivity.io/compat/telnyx/v2. With the upstreamtelnyxpackage setbaseURL(Node) orbase_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 and the
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
urivalues (relative to/compat/twilio), not Telnyx's/v2/texmlURIs. - Phone numbers: US only and voice-only during beta; the search filters listed above only.
- Error bodies follow each vendor's shape;
more_infolinks point to the vendor's error pages.

