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 optional

Telnyx'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_codeMeaning
invalid_api_keyThe key does not authenticate against the provider.
invalid_applicationThe key authenticates but the application id or SID is wrong or disabled.
application_disabledThe application exists but is disabled provider-side.
invalid_connectionThe application exists but the connection id does not belong to it.
unexpected_responseThe provider API was reachable but returned a shape we did not expect.
network_timeoutNetwork timeout, TLS failure, or DNS failure.
rate_limitedThe provider rate-limited the validation call. Retry with backoff.
unknown_errorUnclassified — 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"
}
FieldNotes
providerOne of telnyx, twilio, plivo.
priorityInteger 0–10000, default 100. Lower is preferred — the router sorts ascending.
enabledDefaults to true. A disabled connection is filtered out before routing.
verified_atWhen credentials last validated successfully. Null until they do.
circuit_stateclosed, open, or half_open — see below.
failure_countConsecutive dispatch failures recorded against this tenant-and-provider pair.
last_failure_at / last_success_atTimestamps 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:

  1. Drop providers with no configured credentials.
  2. Drop providers whose circuit is open.
  3. Drop providers the caller explicitly excluded — used on retries.
  4. Sort by priority ascending. A caller-supplied preferred provider moves to the front if it survived the filters.
  5. 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.

TransitionTrigger
closed → openFive consecutive failed dispatches.
open → half_openSixty seconds elapsed since the circuit opened.
half_open → closedOne successful dispatch.
half_open → openThe 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

StatusCodeMeaning
412no_providers_configuredThe 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.
503provider_unavailableThe upstream provider is failing or its circuit is open.

Next