The /api/v1/sip routes are the telephony control-plane surface CXB API exposes to two consumers: CXB Core (internal SIP lookups, protected by X-CXB-Core-Secret) and CRMs/admins (dialout, protected by API key or JWT). These routes live in the API service’s SIP module, with API-key auth and the SIP-header service.

Endpoints

MethodPathAuthPurpose
GET/api/v1/sip/lookup?number=X-CXB-Core-SecretInbound DID → {bot_id, carrier_id, did_id}
GET/api/v1/sip/outbound-trunk?from_number=X-CXB-Core-SecretOutbound trunk + rate limit for dialout
POST/api/v1/sip/dialoutX-API-Key or Bearer JWTTrigger an outbound call
The two GET lookups verify the shared internal secret, rejecting any request whose X-CXB-Core-Secret header does not match the configured core_secret with 403. They delegate to the carrier-service lookup_did and lookup_outbound_trunk helpers, returning 404 when nothing matches.

Dual authentication

/dialout depends on the require_admin_or_api_key dependency:
  • API keys are looked up by SHA-256 hash (_hash_api_key); raw keys are never stored. See API keys.
  • last_used_at is updated via asyncio.create_task, so it never blocks the dialout path.
  • expires_at is coerced to UTC; a malformed expiry yields 403 API key has invalid expiry.

Dialout flow

proxy_dialout body (DialoutRequest): bot_id (required), to_number (required), from_number?, connected_event?, variables?.
  1. Load core_fleet from system_settings; 503 if empty.
  2. pick_fleet_server chooses the healthiest fleet host; 503 if none healthy. See fleet routing.
  3. Reject 422 if both variables and connected_event are sent.
  4. Map variablesconnected_event (CRM-friendly alias) in the proxied payload.
  5. Inject config_url = <request base URL>/api/v1/config so the shared fleet fetches config from this CXB API instance.
  6. POST {fleet}/livekit/dialout with X-CXB-Core-Secret and a 60s timeout. A non-200 is surfaced with the fleet’s status code and its error field.
The variables alias is the field CRMs send. Internally CXB Core only understands connected_event, so CXB API renames it before proxying. Sending both is a client error (422).

SIP header passthrough

Inbound SIP X-headers are turned into prompt variables by the SIP-header service, consumed by the config service (filter_sip_headers(... config=bot["sip_header_config"])). The carrier’s inbound trunk only forwards the headers in DEFAULT_INBOUND_HEADERS_TO_ATTRIBUTES; the bot’s sip_header_config then gates which of those reach the prompt. filter_sip_headers behaviour:
BehaviourDetail
Disabled configReturns empty {headers, variables, transfer_headers}
Candidate filterOnly keys starting with sip.h., x-, or x_ are considered
Normalizationnormalize_header_key strips the sip.h. prefix, lowercases, collapses non-alphanumerics to _
Variable namingvariable_name_for_headersip_header_<normalized>
Value capValues truncated to max_value_length (256 chars)
AllowlistOnly headers in allowed_headers are emitted
Transfertransfer_passthrough_headers (subset of allowed) are echoed on transfer

Carriers & trunks

DID → trunk mapping and outbound trunk selection.

API keys

CRM API-key lifecycle and hashing.

Fleet routing

Best-server selection and config_url passthrough.

CRM dialout API

Operator/CRM-facing dialout usage.