carriers MongoDB collection and mirrors them into LiveKit as SIP inbound/outbound trunks and a dispatch rule. Inbound calls map a dialed DID to a bot; outbound dialout selects a trunk by the from_number.
The carrier and trunk-sync logic lives in the API service.
Endpoints
All routes are under/api/v1/carriers. Reads require admin; all writes require super_admin.
| Method | Path | Role | Purpose |
|---|---|---|---|
POST | / | super_admin | Create carrier + sync trunks |
GET | / | admin | List carriers |
GET | /{carrier_id} | admin | Get one carrier |
PUT | /{carrier_id} | super_admin | Update carrier + re-sync trunk auth/address |
DELETE | /{carrier_id} | super_admin | Delete carrier + clean up LiveKit resources |
POST | /{carrier_id}/dids | super_admin | Add a DID, re-sync trunk numbers |
PUT | /{carrier_id}/dids/{did_id} | super_admin | Update a DID (re-syncs numbers if direction changed) |
DELETE | /{carrier_id}/dids/{did_id} | super_admin | Remove a DID, re-sync trunk numbers |
POST | /{carrier_id}/sync | super_admin | Force re-sync against LiveKit |
The LiveKit API client is built per-request from
settings.livekit_url/api_key/api_secret. When livekit_url is unset the carrier is still stored, but its lk_sync_status stays pending. The force-sync endpoint returns 503 LiveKit not configured in that case.Carrier model
Key fields from the carrier model:| Field | Type | Notes |
|---|---|---|
name, sip_address | str | Display name and outbound SIP target |
transport | auto/tcp/udp/tls | Outbound trunk transport |
destination_country | str | Outbound trunk hint |
inbound_auth_mode | credentials/ip | ip requires a non-empty inbound_allowed_addresses |
inbound_auth_username/password, inbound_allowed_addresses | per-direction overrides | Fall back to legacy auth_username/password/allowed_addresses |
outbound_auth_username/password | per-direction overrides | Fall back to legacy auth |
outbound_cps | float (0.1–100) | Carrier dial rate cap (used by CXB Dialler) |
outbound_burst | int (1–1000) | Token-bucket burst |
outbound_channels | int (1–500) | Max simultaneous outbound channels |
dids[] | list | Each DID has number, label, direction (both/inbound/outbound), bot_id |
lk_inbound_trunk_id, lk_outbound_trunk_id, lk_dispatch_rule_id | str/null | LiveKit resource IDs |
lk_sync_status | pending/synced/error | Last sync outcome |
lk_sync_error, lk_last_synced_at | str/datetime | Diagnostics |
Trunk sync
_sync_create_trunks partitions DID numbers by direction and creates LiveKit resources:
- Inbound numbers = DIDs with direction
inboundorboth; outbound numbers =outboundorboth. - Inbound trunk auth/allowlist resolved by
_resolve_inbound_auth(mode-aware); outbound by_resolve_outbound_auth. - The inbound trunk declares
headers_to_attributesfromDEFAULT_INBOUND_HEADERS_TO_ATTRIBUTES(e.g.X-Customer-Name,X-Lead-Id,X-Call-Ref,X-Campaign-Id), mapping each to thesip.h.<Header>attribute convention. LiveKit drops any X-header not listed here. - The dispatch rule uses
SIPDispatchRuleIndividual(room_prefix="call-"), so inbound SIP calls land incall-*rooms — the prefix the LiveKit webhook handler keys on.
Updates and re-sync
update_carrierre-syncs trunk auth/address/transport via_sync_update_trunks. Inbound trunk replacement usesupdate_inbound_trunk_auth, which fetches the existing trunk and rebuilds it via the Replace API soheaders_to_attributessurvives (LiveKit’s field-update SDK cannot set it).- DID add/update/remove re-sync trunk numbers via
_sync_did_numbers(andupdate_trunk_numbers, which silently no-ops if the LiveKit build does not support number updates). sync_carrierdistinguishes a genuinely partial state from a legitimately inbound-less carrier (inbound_complete/dispatch_completeare true when there are no inbound DIDs) so re-sync does not needlessly tear down a working outbound trunk. If an update reports a stale/not_foundtrunk, it deletes the stale IDs and recreates.
Trunk health loop
verify_and_repair_trunks lists live LiveKit trunk and dispatch-rule IDs, compares each carrier’s stored lk_* IDs against them, and calls sync_carrier for any carrier whose IDs are missing. Carriers in pending are skipped. It returns the count repaired. This runs as the periodic trunk health loop registered in app.py; a missing LiveKit connection makes it a no-op (returns 0).
Lookups for the call path
The SIP-header service and the SIP routes consume these helpers:| Helper | Used by | Returns |
|---|---|---|
lookup_did(number) | inbound SIP dispatch | {bot_id, carrier_id, did_id} for an inbound/both DID that has a bot_id |
lookup_outbound_trunk(from_number) | dialout | {trunk_id, number, carrier_id, carrier_name, rate_limit} for an outbound/both DID (first available if from_number omitted) |
rate_limit is derived by _carrier_rate_limit from outbound_cps/outbound_burst/outbound_channels and is consumed by CXB Dialler’s per-carrier rate limiter.
Related docs
Inbound SIP dispatch
How a
call-* room participant is routed to a fleet worker.SIP dialout
Outbound trunk selection and dual-auth dialout.
LiveKit SIP inbound (CXB Core)
What CXB Core does after dispatch.
Fleet routing
How CXB API picks a fleet worker for each call.