Skip to main content

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

  1. Telephony Router (/routers/telephony.py)

    • REST API endpoints for call control
    • Prometheus metrics integration
    • Error handling and validation
  2. 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
  3. XML-RPC Adapter (/adapters/telephony_xml_rpc.py)

    • Ports & Adapters pattern implementation
    • Retry logic and timeout handling
    • Connection pooling via semaphore (max concurrency)
  4. 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:

  1. Calls the A-leg endpoint
  2. 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 termination
  • USER_BUSY - Called party is busy
  • NO_ANSWER - Called party didn't answer
  • CALL_REJECTED - Call was rejected
  • ORIGINATOR_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}/mute
  • POST /v1/telephony/calls/{uuid}/unmute

7. Hold/Resume

Place call on hold with music.

Endpoints:

  • POST /v1/telephony/calls/{uuid}/hold
  • POST /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 recording
  • duration: 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 identifier
  • agent_id - Assigned agent ID
  • queue_name - Source queue
  • campaign_id - Campaign identifier
  • call_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 call
  • CALL_ORIGINATE_ERROR - Error during origination
  • CALL_NOT_FOUND - Call UUID not found
  • CALL_HANGUP_ERROR - Failed to hangup call
  • CALL_TRANSFER_ERROR - Failed to transfer call
  • CALL_BRIDGE_ERROR - Failed to bridge calls
  • BRIDGE_FAILED - One or both calls not found
  • PLAY_FILE_ERROR - Failed to play audio file
  • RECORD_CALL_ERROR - Failed to start recording
  • STOP_RECORD_ERROR - Failed to stop recording
  • SET_VARIABLE_ERROR - Failed to set variable
  • GET_VARIABLE_ERROR - Failed to get variable
  • VARIABLE_NOT_FOUND - Variable not found on call
  • CHANNEL_NOT_FOUND - Channel UUID not found
  • LIST_CHANNELS_ERROR - Failed to list channels
  • GET_SYSTEM_INFO_ERROR - Failed to get system info
  • GET_REGISTRATIONS_ERROR - Failed to get registrations
  • GET_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

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 call
  • DELETE /v1/telephony/calls/{uuid} - Hangup call
  • POST /v1/telephony/calls/{uuid}/transfer - Transfer call
  • POST /v1/telephony/calls/{uuid}/bridge - Bridge two calls
  • POST /v1/telephony/calls/{uuid}/mute - Mute call
  • POST /v1/telephony/calls/{uuid}/unmute - Unmute call
  • POST /v1/telephony/calls/{uuid}/hold - Hold call
  • POST /v1/telephony/calls/{uuid}/resume - Resume held call
  • POST /v1/telephony/calls/{uuid}/dtmf - Send DTMF tones

Call Control (Alternative API)

  • POST /api/v1/calls/{uuid}/hangup - Hangup call
  • POST /api/v1/calls/{uuid}/transfer - Transfer call
  • POST /api/v1/calls/{uuid}/scheduled-transfer - Schedule transfer
  • POST /api/v1/calls/{uuid}/park - Park call
  • POST /api/v1/calls/{uuid}/break - Break current operation

Audio & Media

  • POST /v1/telephony/calls/{uuid}/play - Play audio file
  • POST /api/v1/calls/{uuid}/broadcast - Broadcast audio
  • POST /api/v1/calls/{uuid}/stop-broadcast - Stop broadcast
  • POST /api/v1/calls/{uuid}/speak - Text-to-speech

Recording

  • POST /v1/telephony/calls/{uuid}/record - Start recording
  • DELETE /v1/telephony/calls/{uuid}/record - Stop recording
  • POST /api/v1/calls/{uuid}/record - Control recording (start/stop/pause/resume)

Channel Variables

  • PUT /v1/telephony/calls/{uuid}/variables/{variable} - Set variable
  • GET /v1/telephony/calls/{uuid}/variables/{variable} - Get variable
  • POST /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 channels
  • GET /v1/telephony/channels/{uuid} - Get channel by UUID
  • GET /v1/telephony/system/info - Get system info (hybrid)
  • GET /v1/telephony/registrations - Get SIP registrations
  • GET /v1/telephony/stats/calls - Get call statistics
  • GET /api/v1/calls/{uuid}/status - Get call status

Scheduled Operations

  • POST /v1/telephony/calls/{uuid}/schedule/hangup - Schedule hangup
  • POST /v1/telephony/calls/{uuid}/schedule/transfer - Schedule transfer
  • POST /v1/telephony/calls/{uuid}/schedule/broadcast - Schedule broadcast

References