Reference
Telephony providers.
Rexa.ai runs the agent; the phone call itself goes out over a carrier account you own. Three providers are supported — Telnyx, Twilio and Plivo — and you can connect more than one and let the platform fail over between them.
Where providers are configured
Provider connections are managed from the dashboard rather than the public /v1 API — they live under the tenant-scoped console routes /v1/_internal/t/:slug/providers and are gated by console permissions, not by API-key scopes. Your API key uses the connection; it does not create it. Once a provider is connected, calls placed with POST /v1/calls route through it automatically.
Credentials by provider
Credentials are stored encrypted and are never returned by any read endpoint. Each provider takes its own shape:
// telnyx
{ "api_key": "...", "voice_application_id": "...", "connection_id": "..." } // connection_id optional
// twilio
{ "account_sid": "AC...", "auth_token": "..." }
// plivo
{ "auth_id": "...", "auth_token": "...", "app_id": "..." } // app_id optionalTelnyx's connection_id is optional. Plivo's app_id is optional and scopes the phone-number inventory this tenant can see to numbers linked to that application — worth setting when one Plivo account hosts several end-customers, because leaving it off shows every number on the account.
Credentials are validated on save
Saving a connection calls the provider's own API to confirm the credentials work — Twilio via GET /2010-04-01/Accounts/{SID}.json, Plivo via GET /v1/Account/{AUTH_ID}/. Transient failures (network timeout, provider rate limit) are retried up to three times with one-second backoff; a deterministic 4xx short-circuits immediately. A failed validation surfaces as 422 with a stable code you can branch on.
| validation_code | Meaning |
|---|---|
| invalid_api_key | The key does not authenticate against the provider. |
| invalid_application | The key authenticates but the application id or SID is wrong or disabled. |
| application_disabled | The application exists but is disabled provider-side. |
| invalid_connection | The application exists but the connection id does not belong to it. |
| unexpected_response | The provider API was reachable but returned a shape we did not expect. |
| network_timeout | Network timeout, TLS failure, or DNS failure. |
| rate_limited | The provider rate-limited the validation call. Retry with backoff. |
| unknown_error | Unclassified — the provider’s own message is passed through. |
The same check can be re-run on demand against a saved connection, which is the fastest way to tell "our credentials rotated" apart from "the carrier is down":
// success
{ "ok": true, "metadata": { "...": "provider account details" } }
// failure
{ "ok": false, "validation_code": "invalid_api_key", "message": "..." }What a connection looks like
{
"id": "...",
"provider": "telnyx",
"priority": 100,
"enabled": true,
"verified_at": "2026-08-08T14:31:02.881Z",
"circuit_state": "closed",
"failure_count": 0,
"last_failure_at": null,
"last_success_at": "2026-08-08T15:02:44.010Z",
"created_at": "2026-08-01T09:14:00.000Z",
"updated_at": "2026-08-08T15:02:44.010Z"
}| Field | Notes |
|---|---|
| provider | One of telnyx, twilio, plivo. |
| priority | Integer 0–10000, default 100. Lower is preferred — the router sorts ascending. |
| enabled | Defaults to true. A disabled connection is filtered out before routing. |
| verified_at | When credentials last validated successfully. Null until they do. |
| circuit_state | closed, open, or half_open — see below. |
| failure_count | Consecutive dispatch failures recorded against this tenant-and-provider pair. |
| last_failure_at / last_success_at | Timestamps of the most recent dispatch outcomes. |
Routing and failover
When more than one provider is connected, the router builds an ordered attempt list for each dispatch:
- Drop providers with no configured credentials.
- Drop providers whose circuit is open.
- Drop providers the caller explicitly excluded — used on retries.
- Sort by
priorityascending. A caller-supplied preferred provider moves to the front if it survived the filters. - Cap the list at three attempts — a primary plus up to two fallbacks.
If nothing survives, the dispatch is refused with one of no_candidates_configured, no_healthy_provider or all_providers_excluded. Campaigns can pin a carrier with preferred_provider (telnyx, twilio or plivo) so a number is dialled through the carrier that actually owns it, which skips priority-based failover.
The circuit breaker
Each tenant-and-provider pair has its own breaker, so one customer's bad credentials never take another's carrier out of rotation. State is held in Redis on the hot path and mirrored to Postgres so a cache flush does not lose it.
| Transition | Trigger |
|---|---|
| closed → open | Five consecutive failed dispatches. |
| open → half_open | Sixty seconds elapsed since the circuit opened. |
| half_open → closed | One successful dispatch. |
| half_open → open | The probe failed. The sixty-second timer restarts. |
closed and half_open admit dispatches; open refuses them until the cooldown elapses. Only one probe is admitted while half-open.
Errors you will see on the API
| Status | Code | Meaning |
|---|---|---|
| 412 | no_providers_configured | The tenant has no enabled and verified telecom provider. Connect one before dispatching. |
| 422 | (validation_code) | Credential validation failed against the provider — see the table above. |
| 503 | provider_unavailable | The upstream provider is failing or its circuit is open. |
Next
- Outbound calls — dispatch through a connected provider.
- WebRTC rooms — browser conversations that need no carrier at all.
- Compliance controls — the checks that run before a call reaches your carrier.