This page documents the API contracts between CXB Core and external systems. Use this if you’re building your own orchestrator (replacing CXB API) or integrating CXB Core into an existing platform.

How CXB Core works

CXB Core is a stateless pipeline worker. It doesn’t store bots, users, or call history. For each call:
  1. It fetches bot configuration from a URL you provide (CONFIG_URL)
  2. It runs the voice pipeline (STT → LLM → TTS)
  3. It persists and POSTs call results to the webhook_url from the config
You implement two endpoints:
  • Config endpoint — CXB Core calls this to get bot config at call start
  • Results webhook — CXB Core calls this to deliver call results at call end

1. Config endpoint (you implement)

CXB Core fetches bot configuration at the start of every call.

Request (CXB Core → your system)

GET {CONFIG_URL}/{bot_id}?caller_id={caller}&stream_id={stream}&connected_event={json}
X-CXBCore-Secret: {secret}
ParameterSourceDescription
bot_idURL path from WebSocket route or dialout requestWhich bot to load
caller_idDialler handshake or SIP headersCustomer phone number
stream_idDialler handshakeUnique call identifier from the dialler
connected_eventURL-encoded JSONFull handshake payload from dialler, attach, or dialout request (CRM fields, call direction, etc.)
X-CXBCore-Secret.env on fleet serverShared secret for authentication

Response (your system → CXB Core)

Return 200 with the full bot config JSON. See Bot config schema for the complete field reference. Minimal example:
{
  "session_id": "uuid-you-generate",
  "webhook_url": "https://your-api.com/call-results",
  "system_prompt": "You are a helpful assistant...",
  "opening_message": "Hello! How can I help you today?",
  "stt": {
    "provider": "stt_streaming",
    "api_key": "your-stt-key",
    "model": "<model-id>",
    "language": "en"
  },
  "llm": {
    "provider": "llm_a",
    "api_key": "your-llm-key",
    "model": "your-model",
    "temperature": 0.7,
    "max_tokens": 256
  },
  "tts": {
    "provider": "tts_a",
    "api_key": "your-tts-key",
    "voice_id": "your-voice-id",
    "model": "your-model",
    "language": "en"
  }
}
Error responses:
StatusMeaningCXB Core behavior
200Config returnedCall proceeds
404Bot not foundCall rejected, WebSocket closed
503Outside active hoursCall rejected, WebSocket closed
OtherErrorCall rejected, WebSocket closed
The session_id you return must be unique per call. CXB Core uses it to correlate the results webhook back to this call. Generate a UUID.

2. Results webhook (you implement)

CXB Core POSTs call results when a call ends. The URL comes from webhook_url in the config response.

Request (CXB Core → your system)

POST {webhook_url}
Content-Type: application/json
X-CXBCore-Secret: {secret}

Payload

{
  "session_id": "uuid-from-config",
  "stream_id": "dialler-stream-id",
  "caller_id": "+919876543210",
  "from_number": "+911234567890",
  "call_duration_seconds": 141.1,
  "call_direction": "inbound",
  "disconnected_by": "customer",
  "transcript": [
    {"speaker": "bot", "text": "Hello! How can I help?", "ts": 2.1, "language": "en", "finalized": true, "stt": {"confidence": 0.97}},
    {"speaker": "customer", "text": "Hi, I have a question", "ts": 5.3, "language": "hi", "finalized": true},
    {"speaker": "bot", "text": "Sure, go ahead!", "ts": 7.8}
  ],
  "recording_url": "https://storage.example.com/recording.wav",
  "recording_key": "calls/session-id.wav",
  "post_call_analysis": {
    "disposition": "INTERESTED",
    "summary": "Customer asked about pricing..."
  },
  "qc_analysis": null,
  "was_transferred": false,
  "transfer_destination": "",
  "transfer_target": "",
  "transfer_reason": "",
  "transfer_method": "",
  "transfer_at": null,
  "transfer_failed_reason": "",
  "latency": {
    "stt_avg_ms": 450.0,
    "rag_avg_ms": 0.0,
    "tool_avg_ms": 0.0,
    "llm_avg_ms": 520.0,
    "tts_avg_ms": 200.0,
    "total_avg_response_ms": 1170.0
  },
  "events": [
    {"event": "connected", "ts": 0.0},
    {"event": "pipeline_started", "ts": 0.5},
    {"event": "opening_message_queued", "ts": 0.5},
    {"event": "disconnected", "ts": 141.0, "by": "customer"},
    {"event": "pipeline_finished", "ts": 141.1}
  ],
  "usage_metrics": [
    {"type": "llm", "processor": "LLMService", "model": "your-model", "prompt_tokens": 8000, "completion_tokens": 150, "total_tokens": 8150, "cache_read_input_tokens": 7800, "cache_creation_input_tokens": 0, "cache": {"enabled": true, "status": "hit", "namespace": "live_prompt", "version": "v3", "reason": null}},
    {"type": "tts", "processor": "TTSService", "model": "your-model", "characters": 450},
    {"type": "ttfb", "processor": "TTSService", "ttfb_ms": 310.0}
  ],
  "pipeline_config": {
    "transport_type": "websocket",
    "stt_provider": "stt_streaming",
    "stt_model": "<model-id>",
    "llm_provider": "llm_a",
    "llm_model": "your-model",
    "tts_provider": "tts_a",
    "tts_model": "your-model",
    "tts_voice_id": "your-voice-id",
    "tts_language": "en"
  }
}

Field reference

FieldTypeDescription
session_idstringSession ID from config response
stream_idstringDialler-assigned call identifier
caller_idstringCustomer phone number
from_numberstring/nullOutbound caller ID / DID used for the call
call_duration_secondsfloatTotal call length in seconds
call_directionstring"inbound" or "outbound"
disconnected_bystringWho ended the call (see table below)
transcriptlistConversation transcript (see Transcript tab)
recording_urlstringURL to call recording (WAV)
recording_keystringS3 key for generating signed URLs

usage_metrics entries

Each entry records usage for one pipeline step.
FieldTypeDescription
typestringllm, tts, ttfb, post_call, or qc. Callback extraction is emitted as type=post_call with processor=callback_extraction.
processorstring/nullPipeline service class (or post-call stage) that produced the metric
modelstring/nullModel identifier
prompt_tokensint/nullLLM input tokens
completion_tokensint/nullLLM output tokens
total_tokensint/nullLLM total tokens
cache_read_input_tokensint/nullTokens served from cache (cache read)
cache_creation_input_tokensint/nullTokens written when creating/refreshing a cache
cachedict/nullCache metadata (see below)
charactersint/nullTTS character count (TTS only)
ttfb_msfloat/nullTime-to-first-byte in ms (TTFB only)
The cache dict:
KeyTypeDescription
enabledboolWhether caching was active for this step
statusstringhit / miss for live LLM; cache create/reuse status for post-call
namespacestringlive_prompt (first live llm entry), post_call_analysis, qc_analysis, or callback_extraction
versionstringCache version in effect
reasonstring/nullWhy the cache was in this state (e.g. recreated/expired)
For LLM providers that report cache reads, live-prompt hit/miss is derived only from real cache_read_input_tokens (hit when > 0, miss when 0). CXB Core does not estimate cached tokens or money saved. Cache-creation tokens come from the provider’s cache usage metadata. See Caching.

events entries

events is an open-ended list of timestamped lifecycle entries. Every entry has event (string) and ts (float, seconds from call start). Depending on the event type, additional keys may be present:
FieldDescription
byActor for disconnect events (customer, bot, …)
triggerHangup trigger: end_call_tool, closure_phrase, dead_air_timeout, max_duration
functionTool name for tool-call events (end_call, transfer_call, detected_voicemail, custom)
argsArguments passed to the tool
statusTool status, e.g. ok / no_number_configured
transfer_number / transfer_headersTransfer destination and SIP headers
reasonHuman-readable reason (e.g. SIP failure description)
sip_code / sip_statusSIP response code and status text
errorError string or dict (service errors)
duration_ms / duration_seconds / raw_duration_seconds / billable_duration_secondsDuration fields
noteFree-form note

disconnected_by values

ValueMeaning
botBot called end_call tool, closure phrase, or dead air timeout
customerCustomer hung up
voicemailVoicemail detected
RNRDead air — customer picked up but stayed silent
outside_hoursCall rejected — bot outside active hours
no_answerSIP 408/480 — callee didn’t answer (outbound only)
rejectedSIP 486/603 — callee rejected (outbound only)
timeoutMax call duration exceeded
transfer_to_agentCall handed off to Agent Desk; route timeout must not tear it down
errorPipeline startup failure or unhandled exception

Your response

Return 200 to acknowledge. CXB Core first writes results to a local durable outbox, then attempts delivery. Transient transport failures and 5xx responses are retried immediately a few times; any failed delivery attempt remains in the outbox and the background worker retries later.

3. Dialout endpoint (on CXB Core)

Trigger outbound calls by POSTing to CXB Core’s dialout endpoint. This requires LiveKit SIP to be configured on the fleet server.

Request (your system → CXB Core)

POST https://fleet.example.com/livekit/dialout
Content-Type: application/json
X-CXBCore-Secret: {secret}
{
  "bot_id": "your-bot-id",
  "to_number": "+919876543210",
  "from_number": "+911234567890",
  "connected_event": {
    "customer_name": "Rahul Sharma",
    "loan_number": "LN123456",
    "amount_due": "15000"
  },
  "config_url": "https://your-api.com/api/v1/config"
}
FieldTypeRequiredDescription
bot_idstringYesBot ID passed to config endpoint
to_numberstringYesCustomer phone number to dial
from_numberstringNoCaller ID / DID. Uses default trunk number if omitted.
connected_eventdictNoCRM data available as {{call.field}} in prompts
config_urlstringNoOverride config endpoint URL. Falls back to the CONFIG_URL env var.
The CXB Core dialout body uses connected_event. variables is a CXB API-only alias — CXB API maps variables to connected_event before proxying to CXB Core. When calling CXB Core directly, send connected_event.
config_url lets a shared CXB Core fleet serve multiple orchestrators. Each orchestrator passes its own config URL so CXB Core fetches the right bot config.

Response

{
  "status": "accepted",
  "call_id": "abc123-def456",
  "room_name": "dialout-abc123-def456"
}
StatusBodyMeaning
200{"status":"accepted","call_id":...,"room_name":...}Call accepted (runs in background)
403{"error":"Unauthorized"}Invalid X-CXBCore-Secret
429{"error":"At capacity"}Worker has no free call slot
503{"error":"LiveKit not configured"}LiveKit SIP not configured on this fleet host
The call runs asynchronously. Results are delivered via the webhook URL from the config response.
For campaign dialing, CXB Dialler usually creates and screens the LiveKit SIP call first, then calls CXB Core POST /attach after the callee answers. Use /livekit/dialout for direct one-off CXB Core outbound calls.

Sequence diagram


Inbound calls (WebSocket)

For inbound WebSocket calls, the dialler connects directly to CXB Core:
wss://fleet.example.com/ws/{bot_id}
See WebSocket protocol for the handshake and message format. The same config fetch and results webhook flow applies — CXB Core calls your config endpoint at call start and your webhook at call end.