Telephony & Call Control
The Telephony & Call Control API provides comprehensive call management capabilities for the Ominis Cluster Manager. This system enables outbound calling, call transfers, bridging, recording, channel variable management, and real-time monitoring.
Architecture Overview
Hybrid READ/WRITE Pattern (ADR-006)
The telephony system uses a hybrid architecture for optimal performance:
- WRITE Operations: Use XML-RPC for call control commands (originate, transfer, hangup, etc.)
- READ Operations: Use direct PostgreSQL queries for monitoring and status checks
Performance Benefits:
- Channel queries: 10-50x faster (5-10ms vs 50-500ms)
- Individual channel lookups: 20-25x faster (2-5ms vs 50-100ms)
- Call statistics: 25-30x faster (2-5ms vs 50-150ms)
- Registration queries: 10-20x faster (5-10ms vs 50-200ms)
Why This Approach?
- FreeSWITCH updates PostgreSQL tables in real-time
- XML-RPC excels at control but is slow for queries
- Database queries scale better for high-frequency monitoring
- Reduces load on FreeSWITCH XML-RPC interface
Key Components
-
Telephony Router (
/routers/telephony.py)- REST API endpoints for call control
- Prometheus metrics integration
- Error handling and validation
-
Call Control Router (
/routers/call_control.py)- UUID-based call operations for n8n/external systems
- Direct XML-RPC command execution
- Simplified API for workflow automation
-
XML-RPC Adapter (
/adapters/telephony_xml_rpc.py)- Ports & Adapters pattern implementation
- Retry logic and timeout handling
- Connection pooling via semaphore (max concurrency)
-
Database Adapter (
/adapters/database.py)- Fast channel/registration queries
- ACL management
- Direct PostgreSQL access
Call Management Operations
1. Originate Call
Create an outbound two-legged call where FreeSWITCH:
- Calls the A-leg endpoint
- When answered, executes the B-leg application
Use Cases:
- Outbound dialer campaigns
- Click-to-call features
- Automated calling systems
Endpoint: POST /v1/telephony/calls/originate
Example: Call Agent, Bridge to Customer
curl -X POST http://api:8000/v1/telephony/calls/originate \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"aleg_endpoint": "user/1000",
"bleg_application": "bridge",
"bleg_args": "sofia/gateway/provider/+15551234567",
"aleg_variables": {
"origination_caller_id_name": "Support Team",
"origination_caller_id_number": "+18005551234"
},
"timeout": 60
}'
Response:
{
"uuid": "12345678-1234-1234-1234-123456789012",
"success": true,
"message": "Call originated successfully: A-leg=user/1000, B-leg=bridge"
}
Call Origination Flow:
2. Hangup Call
Terminate an active call with specified cause code.
Endpoint: DELETE /v1/telephony/calls/{uuid}
Example:
curl -X DELETE "http://api:8000/v1/telephony/calls/12345678-1234-1234-1234-123456789012?cause=NORMAL_CLEARING" \
-H "X-API-Key: your-key"
Common Hangup Causes:
NORMAL_CLEARING- Normal call terminationUSER_BUSY- Called party is busyNO_ANSWER- Called party didn't answerCALL_REJECTED- Call was rejectedORIGINATOR_CANCEL- Caller cancelled
Alternative Endpoint (n8n-friendly):
POST /api/v1/calls/{uuid}/hangup?cause=NORMAL_CLEARING
3. Transfer Call
Transfer a call to a new destination. Supports blind and attended transfers.
Endpoint: POST /v1/telephony/calls/{uuid}/transfer
Example: Blind Transfer to Queue
curl -X POST http://api:8000/v1/telephony/calls/{uuid}/transfer \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"destination": "support_queue",
"dialplan": "XML",
"context": "default"
}'
Blind Transfer Ladder Diagram:
Attended Transfer Ladder Diagram:
Scheduled Transfer (Delayed):
POST /api/v1/calls/{uuid}/scheduled-transfer
{
"destination": "voicemail@default",
"delay_seconds": 30,
"dialplan": "XML",
"context": "default"
}
4. Bridge Calls
Bridge two active calls together.
Endpoint: POST /v1/telephony/calls/{uuid}/bridge
Example:
curl -X POST http://api:8000/v1/telephony/calls/{uuid1}/bridge \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"target_uuid": "87654321-4321-4321-4321-210987654321"
}'
Use Cases:
- Manual call bridging in call center
- Conference call setup
- Call transfer completion
5. Park Call
Place a call in a holding state until further action.
Endpoint: POST /api/v1/calls/{uuid}/park
Example:
curl -X POST http://api:8000/api/v1/calls/{uuid}/park \
-H "X-API-Key: your-key"
Use Cases:
- Hold call while retrieving information
- Queue-like behavior without formal queue
- Manual call routing scenarios
6. Mute/Unmute
Control audio from caller.
Endpoints:
POST /v1/telephony/calls/{uuid}/mutePOST /v1/telephony/calls/{uuid}/unmute
7. Hold/Resume
Place call on hold with music.
Endpoints:
POST /v1/telephony/calls/{uuid}/holdPOST /v1/telephony/calls/{uuid}/resume
8. DTMF Control
Send DTMF tones to call (useful for IVR navigation).
Endpoint: POST /v1/telephony/calls/{uuid}/dtmf
Example:
curl -X POST http://api:8000/v1/telephony/calls/{uuid}/dtmf \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"tones": "1234#"
}'
Alternative (n8n-friendly):
POST /api/v1/calls/{uuid}/send-dtmf
{
"digits": "1234#",
"duration_ms": 100
}
Recording Control
Recording Lifecycle
Start Recording
Endpoint: POST /v1/telephony/calls/{uuid}/record
Example:
curl -X POST http://api:8000/v1/telephony/calls/{uuid}/record \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"file_path": "/var/recordings/call-2025-09-30-12345.wav",
"duration": 0
}'
Parameters:
file_path: Absolute path to save recordingduration: Recording duration in seconds (0 = unlimited, max 3600)
Alternative (n8n-friendly with variable substitution):
POST /api/v1/calls/{uuid}/record
{
"action": "start",
"file_path": "/recordings/${uuid}.wav"
}
(The ${uuid} will be automatically replaced)
Stop Recording
Endpoint: DELETE /v1/telephony/calls/{uuid}/record
Example:
curl -X DELETE http://api:8000/v1/telephony/calls/{uuid}/record \
-H "X-API-Key: your-key"
Alternative (n8n-friendly):
POST /api/v1/calls/{uuid}/record
{
"action": "stop",
"file_path": "/recordings/${uuid}.wav"
}
Pause/Resume Recording
Only available via Call Control API:
Pause:
POST /api/v1/calls/{uuid}/record
{
"action": "pause",
"file_path": "/recordings/${uuid}.wav"
}
Resume:
POST /api/v1/calls/{uuid}/record
{
"action": "resume",
"file_path": "/recordings/${uuid}.wav"
}
Use Cases:
- Compliance (pause during sensitive information)
- Quality management
- Legal requirements
- Training and evaluation
Channel Variables
Channel variables store metadata and control call behavior.
Set Variable
Endpoint: PUT /v1/telephony/calls/{uuid}/variables/{variable}
Example:
curl -X PUT http://api:8000/v1/telephony/calls/{uuid}/variables/customer_id \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"variable": "customer_id",
"value": "CUST-12345"
}'
Alternative (n8n-friendly):
POST /api/v1/calls/{uuid}/set-variable
{
"name": "customer_id",
"value": "CUST-12345"
}
Get Variable
Endpoint: GET /v1/telephony/calls/{uuid}/variables/{variable}
Example:
curl -X GET http://api:8000/v1/telephony/calls/{uuid}/variables/customer_id \
-H "X-API-Key: your-key"
Response:
{
"uuid": "12345678-1234-1234-1234-123456789012",
"variable": "customer_id",
"value": "CUST-12345"
}
Alternative (n8n-friendly):
GET /api/v1/calls/{uuid}/get-variable/customer_id
Common Variables:
customer_id- Customer identifieragent_id- Assigned agent IDqueue_name- Source queuecampaign_id- Campaign identifiercall_priority- Priority level (1-10)custom_*- Application-specific variables
Variable Use Cases:
- CDR enrichment
- Dialplan routing
- Application state
- Call tracking
- Analytics
Channel Monitoring
List All Channels (FAST)
Get all active channels using direct database query.
Endpoint: GET /v1/telephony/channels
Example:
curl -X GET http://api:8000/v1/telephony/channels \
-H "X-API-Key: your-key"
Response:
{
"channels": [
{
"uuid": "12345678-1234-1234-1234-123456789012",
"state": "CS_EXECUTE",
"cid_num": "+15551234567",
"dest": "1000",
"application": "bridge",
"created_epoch": 1696089600
}
],
"total": 1
}
Performance: 10-50x faster than XML-RPC (5-10ms vs 50-500ms)
Get Channel by UUID (FAST)
Get detailed information about a specific channel.
Endpoint: GET /v1/telephony/channels/{uuid}
Example:
curl -X GET http://api:8000/v1/telephony/channels/{uuid} \
-H "X-API-Key: your-key"
Response:
{
"uuid": "12345678-1234-1234-1234-123456789012",
"state": "CS_EXECUTE",
"cid_num": "+15551234567",
"cid_name": "John Doe",
"dest": "1000",
"application": "bridge",
"application_data": "user/1000",
"read_codec": "PCMU",
"write_codec": "PCMU",
"created_epoch": 1696089600
}
Performance: 20-25x faster than XML-RPC (2-5ms vs 50-100ms)
Alternative (n8n-friendly):
GET /api/v1/calls/{uuid}/status
Get System Information (HYBRID)
Get FreeSWITCH system information and statistics.
Endpoint: GET /v1/telephony/system/info
Example:
curl -X GET http://api:8000/v1/telephony/system/info \
-H "X-API-Key: your-key"
Response:
{
"uptime": "0 years, 5 days, 12 hours, 34 minutes, 56 seconds",
"version": "FreeSWITCH Version 1.10.7",
"max_sessions": "1000",
"current_sessions": "42",
"peak_sessions": "89",
"last_idle": "2025-09-30T10:30:45Z"
}
Performance: Session counts from database (fast), uptime/version from XML-RPC (acceptable)
Get SIP Registrations (FAST)
List all registered SIP endpoints.
Endpoint: GET /v1/telephony/registrations
Example:
curl -X GET http://api:8000/v1/telephony/registrations \
-H "X-API-Key: your-key"
Response:
[
{
"contact": "sip:1000@192.168.1.100:5060",
"user": "1000",
"host": "pbx.example.com",
"status": "registered"
}
]
Performance: 10-20x faster than XML-RPC (5-10ms vs 50-200ms)
Get Call Statistics (FAST)
Get aggregate call statistics.
Endpoint: GET /v1/telephony/stats/calls
Example:
curl -X GET http://api:8000/v1/telephony/stats/calls \
-H "X-API-Key: your-key"
Response:
{
"total_calls": 1542,
"active_calls": 42,
"peak_calls": 89,
"failed_calls": 15
}
Performance: 25-30x faster than XML-RPC (2-5ms vs 50-150ms)
Play & Broadcast
Play Audio File
Play audio file to a call.
Endpoint: POST /v1/telephony/calls/{uuid}/play
Example:
curl -X POST http://api:8000/v1/telephony/calls/{uuid}/play \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"file_path": "/usr/share/freeswitch/sounds/en/us/callie/ivr/8000/ivr-welcome.wav",
"leg": "both"
}'
Parameters:
file_path: Path to audio file (local or HTTP URL)leg: Which leg to play to (aleg,bleg,both)
Broadcast Audio (Over Current Audio)
Broadcast audio that plays over current audio (doesn't interrupt).
Endpoint: POST /api/v1/calls/{uuid}/broadcast
Example:
curl -X POST http://api:8000/api/v1/calls/{uuid}/broadcast \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"path": "https://cdn.example.com/hold-music.mp3",
"legs": "aleg",
"loop": true
}'
Stop Broadcast:
POST /api/v1/calls/{uuid}/stop-broadcast
Text-to-Speech
Speak text to call using Piper TTS.
Endpoint: POST /api/v1/calls/{uuid}/speak
Example:
curl -X POST http://api:8000/api/v1/calls/{uuid}/speak \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"text": "Your current balance is $1,234.56",
"engine": "piper",
"voice": "en_US-lessac-medium"
}'
Scheduled Operations
Schedule Hangup
Schedule a call to hangup after delay.
Endpoint: POST /v1/telephony/calls/{uuid}/schedule/hangup
Example:
curl -X POST http://api:8000/v1/telephony/calls/{uuid}/schedule/hangup \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"delay_seconds": 300,
"cause": "NORMAL_CLEARING"
}'
Use Cases:
- Campaign time limits
- Demo call timeouts
- Resource management
Schedule Transfer
Schedule a call transfer after delay.
Endpoint: POST /v1/telephony/calls/{uuid}/schedule/transfer
Example:
curl -X POST http://api:8000/v1/telephony/calls/{uuid}/schedule/transfer \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"delay_seconds": 60,
"destination": "voicemail",
"dialplan": "XML",
"context": "default"
}'
Use Cases:
- "We'll transfer you in 30 seconds"
- Automatic escalation
- Time-based routing
Schedule Broadcast
Schedule audio broadcast after delay.
Endpoint: POST /v1/telephony/calls/{uuid}/schedule/broadcast
Example:
curl -X POST http://api:8000/v1/telephony/calls/{uuid}/schedule/broadcast \
-H "Content-Type: application/json" \
-H "X-API-Key: your-key" \
-d '{
"delay_seconds": 30,
"app_name": "playback",
"app_args": "/usr/share/freeswitch/sounds/en/us/callie/ivr/8000/ivr-call_will_be_transferred.wav",
"leg": "both"
}'
Use Cases:
- Delayed announcements
- Time-based messages
- Campaign notifications
Error Handling
Common HTTP Status Codes
- 200 OK - Operation successful
- 400 Bad Request - Invalid request parameters
- 401 Unauthorized - Missing or invalid API key
- 404 Not Found - Call UUID not found
- 500 Internal Server Error - XML-RPC or system error
Error Response Format
{
"detail": {
"code": "CALL_NOT_FOUND",
"message": "Call 12345678-1234-1234-1234-123456789012 not found or already terminated",
"details": {
"error": "Channel not found"
}
}
}
Common Error Codes
CALL_ORIGINATE_FAILED- Failed to originate callCALL_ORIGINATE_ERROR- Error during originationCALL_NOT_FOUND- Call UUID not foundCALL_HANGUP_ERROR- Failed to hangup callCALL_TRANSFER_ERROR- Failed to transfer callCALL_BRIDGE_ERROR- Failed to bridge callsBRIDGE_FAILED- One or both calls not foundPLAY_FILE_ERROR- Failed to play audio fileRECORD_CALL_ERROR- Failed to start recordingSTOP_RECORD_ERROR- Failed to stop recordingSET_VARIABLE_ERROR- Failed to set variableGET_VARIABLE_ERROR- Failed to get variableVARIABLE_NOT_FOUND- Variable not found on callCHANNEL_NOT_FOUND- Channel UUID not foundLIST_CHANNELS_ERROR- Failed to list channelsGET_SYSTEM_INFO_ERROR- Failed to get system infoGET_REGISTRATIONS_ERROR- Failed to get registrationsGET_CALL_STATS_ERROR- Failed to get statistics
Performance Considerations
XML-RPC Configuration
The XML-RPC adapter includes:
- Connection pooling via semaphore
- Retry logic with configurable attempts
- Timeout handling (default: 30 seconds)
- Max concurrency limit (default: 100)
Configuration:
XMLRPC_TIMEOUT_SECONDS=30
XMLRPC_MAX_RETRIES=3
XMLRPC_MAX_CONCURRENCY=100
Database Query Performance
Direct PostgreSQL queries provide:
- Sub-10ms response times for most operations
- No XML-RPC overhead
- Better scalability for high-frequency polling
- Real-time accuracy (FreeSWITCH updates tables instantly)
When to Use Which Approach
Use XML-RPC for:
- Call control operations (originate, hangup, transfer, etc.)
- Channel variable manipulation
- Audio playback/recording control
- Any operation that modifies call state
Use Database for:
- Channel listing and status checks
- Registration queries
- Call statistics
- System monitoring
- Any read-only operation with high frequency
Prometheus Metrics
The telephony router exports Prometheus metrics:
Counters
telephony_operations_total{operation, status}- Total operations by type and status- Operations:
originate,hangup,transfer,bridge,play,record_start,record_stop,set_var,get_var,get_system_info,get_registrations,get_call_stats - Status:
started,success,error,not_found
- Operations:
Gauges
active_calls_gauge- Current number of active calls
Metrics Endpoint: GET /metrics
Integration Examples
n8n Workflow: Outbound Campaign
{
"nodes": [
{
"type": "HTTP Request",
"name": "Originate Call",
"url": "http://api:8000/v1/telephony/calls/originate",
"method": "POST",
"body": {
"aleg_endpoint": "{{$json.agent_endpoint}}",
"bleg_application": "bridge",
"bleg_args": "{{$json.customer_phone}}",
"aleg_variables": {
"customer_id": "{{$json.customer_id}}",
"campaign_id": "CAMPAIGN-001"
}
}
},
{
"type": "HTTP Request",
"name": "Start Recording",
"url": "http://api:8000/v1/telephony/calls/{{$json.uuid}}/record",
"method": "POST",
"body": {
"file_path": "/recordings/{{$json.uuid}}.wav",
"duration": 0
}
}
]
}
Python Client Example
import httpx
import asyncio
class TelephonyClient:
def __init__(self, base_url: str, api_key: str):
self.base_url = base_url
self.headers = {"X-API-Key": api_key}
async def originate_call(
self,
aleg_endpoint: str,
bleg_application: str,
bleg_args: str,
**variables
):
async with httpx.AsyncClient() as client:
response = await client.post(
f"{self.base_url}/v1/telephony/calls/originate",
headers=self.headers,
json={
"aleg_endpoint": aleg_endpoint,
"bleg_application": bleg_application,
"bleg_args": bleg_args,
"aleg_variables": variables,
}
)
response.raise_for_status()
return response.json()
async def get_channels(self):
async with httpx.AsyncClient() as client:
response = await client.get(
f"{self.base_url}/v1/telephony/channels",
headers=self.headers
)
response.raise_for_status()
return response.json()
# Usage
async def main():
client = TelephonyClient("http://api:8000", "your-api-key")
# Originate call
result = await client.originate_call(
aleg_endpoint="user/1000",
bleg_application="bridge",
bleg_args="user/2000",
customer_id="CUST-12345"
)
print(f"Call UUID: {result['uuid']}")
# List channels
channels = await client.get_channels()
print(f"Active calls: {channels['total']}")
asyncio.run(main())
API Endpoints Summary
Call Management
POST /v1/telephony/calls/originate- Originate outbound callDELETE /v1/telephony/calls/{uuid}- Hangup callPOST /v1/telephony/calls/{uuid}/transfer- Transfer callPOST /v1/telephony/calls/{uuid}/bridge- Bridge two callsPOST /v1/telephony/calls/{uuid}/mute- Mute callPOST /v1/telephony/calls/{uuid}/unmute- Unmute callPOST /v1/telephony/calls/{uuid}/hold- Hold callPOST /v1/telephony/calls/{uuid}/resume- Resume held callPOST /v1/telephony/calls/{uuid}/dtmf- Send DTMF tones
Call Control (Alternative API)
POST /api/v1/calls/{uuid}/hangup- Hangup callPOST /api/v1/calls/{uuid}/transfer- Transfer callPOST /api/v1/calls/{uuid}/scheduled-transfer- Schedule transferPOST /api/v1/calls/{uuid}/park- Park callPOST /api/v1/calls/{uuid}/break- Break current operation
Audio & Media
POST /v1/telephony/calls/{uuid}/play- Play audio filePOST /api/v1/calls/{uuid}/broadcast- Broadcast audioPOST /api/v1/calls/{uuid}/stop-broadcast- Stop broadcastPOST /api/v1/calls/{uuid}/speak- Text-to-speech
Recording
POST /v1/telephony/calls/{uuid}/record- Start recordingDELETE /v1/telephony/calls/{uuid}/record- Stop recordingPOST /api/v1/calls/{uuid}/record- Control recording (start/stop/pause/resume)
Channel Variables
PUT /v1/telephony/calls/{uuid}/variables/{variable}- Set variableGET /v1/telephony/calls/{uuid}/variables/{variable}- Get variablePOST /api/v1/calls/{uuid}/set-variable- Set variable (alternative)GET /api/v1/calls/{uuid}/get-variable/{name}- Get variable (alternative)
Monitoring (FAST - Database)
GET /v1/telephony/channels- List all channelsGET /v1/telephony/channels/{uuid}- Get channel by UUIDGET /v1/telephony/system/info- Get system info (hybrid)GET /v1/telephony/registrations- Get SIP registrationsGET /v1/telephony/stats/calls- Get call statisticsGET /api/v1/calls/{uuid}/status- Get call status
Scheduled Operations
POST /v1/telephony/calls/{uuid}/schedule/hangup- Schedule hangupPOST /v1/telephony/calls/{uuid}/schedule/transfer- Schedule transferPOST /v1/telephony/calls/{uuid}/schedule/broadcast- Schedule broadcast
Related Documentation
- Queue Management - Queue operations and agent management
- Extension Management - SIP extension management
- Campaign Management - Outbound dialer campaigns
- IVR System - Interactive voice response
- Directory XML-CURL - Dynamic SIP authentication
References
- FreeSWITCH XML-RPC API: https://freeswitch.org/confluence/display/FREESWITCH/mod_xml_rpc
- FreeSWITCH Channel Variables: https://freeswitch.org/confluence/display/FREESWITCH/Channel+Variables
- Call Recording: https://freeswitch.org/confluence/display/FREESWITCH/mod_dptools:+record
- Originate Command: https://freeswitch.org/confluence/display/FREESWITCH/mod_commands#originate