Calls API
Outbound Call
Initiate an immediate outbound call via LiveKit with real-time room creation
Outbound Call
Initiate an immediate outbound call using LiveKit infrastructure. This endpoint creates a LiveKit room and establishes a real-time voice connection with the contact.
Endpoint
POST /api/v1/calls/outboundAuthentication
Supports both API key and session-based authentication:
# API Key
x-api-key: YOUR_API_KEY
# Bearer Token
Authorization: Bearer YOUR_API_KEY
# Session (for web UI)
Cookie: session=...Request Body
All fields are validated using Zod schema for type safety.
Required Fields
| Field | Type | Description |
|---|---|---|
agentId | string (UUID) | ID of the voice agent to use |
phoneNumber | string | Contact phone number in E.164 format |
Optional Fields
| Field | Type | Default | Description |
|---|---|---|---|
callerId | string | Agent's first phone | Specific phone number to display as caller ID |
metadata | object | Custom metadata for the call | |
priority | integer (0-10) | 5 | Call priority level |
Validation
Phone Number Validation (Zod)
Phone number must match E.164 format:
- Pattern:
^\+[1-9]\d{1,14}$ - Error message: "Phone number must be in E.164 format (+1234567890)"
Agent ID Validation (Zod)
- Must be a valid UUID
- Error message: "Invalid agent ID format"
Priority Validation (Zod)
- Must be integer between 0 and 10
- Default: 5
Response
Returns call details including LiveKit room information.
Response Schema
{
"data": {
"id": "string",
"status": "connecting",
"agentId": "string",
"phoneNumber": "string",
"callerId": "string",
"roomId": "string",
"roomName": "string",
"dispatchId": "string",
"sipCallId": "string",
"startedAt": "string",
"agent": {
"id": "string",
"name": "string",
"type": "voice"
},
"metadata": {
"caller_id": "string",
"from_phone_number": "string",
"priority": "integer"
}
},
"message": "Outbound call initiated successfully via LiveKit"
}Field Descriptions
id: Unique call log identifierstatus: Always "connecting" for newly initiated callsagentId: Agent UUIDphoneNumber: Contact's phone numbercallerId: Displayed caller IDroomId: LiveKit room identifierroomName: LiveKit room name (same as roomId)dispatchId: LiveKit dispatch/job IDsipCallId: SIP participant identifierstartedAt: Call initiation timestamp (ISO 8601)agent: Agent information objectmetadata: Call metadata including source and priority
Examples
Basic Outbound Call
curl -X POST https://v2.heppu.ai/api/v1/calls/outbound \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agentId": "550e8400-e29b-41d4-a716-446655440000",
"phoneNumber": "+14155552671"
}'const response = await fetch('https://v2.heppu.ai/api/v1/calls/outbound', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
agentId: '550e8400-e29b-41d4-a716-446655440000',
phoneNumber: '+14155552671'
})
});
const data = await response.json();
console.log('Call initiated:', data.data);
console.log('Room ID:', data.data.roomId);import requests
response = requests.post(
'https://v2.heppu.ai/api/v1/calls/outbound',
headers={'x-api-key': 'YOUR_API_KEY'},
json={
'agentId': '550e8400-e29b-41d4-a716-446655440000',
'phoneNumber': '+14155552671'
}
)
data = response.json()
print(f"Call initiated: {data['data']['id']}")
print(f"Room ID: {data['data']['roomId']}")Call with Custom Caller ID and Metadata
curl -X POST https://v2.heppu.ai/api/v1/calls/outbound \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agentId": "550e8400-e29b-41d4-a716-446655440000",
"phoneNumber": "+14155552671",
"callerId": "+14155551234",
"priority": 0,
"metadata": {
"customerName": "John Doe",
"accountId": "ACC-12345",
"callReason": "urgent_support"
}
}'const response = await fetch('https://v2.heppu.ai/api/v1/calls/outbound', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
agentId: '550e8400-e29b-41d4-a716-446655440000',
phoneNumber: '+14155552671',
callerId: '+14155551234',
priority: 0,
metadata: {
customerName: 'John Doe',
accountId: 'ACC-12345',
callReason: 'urgent_support'
}
})
});
const data = await response.json();
console.log('Urgent call initiated:', data.data);response = requests.post(
'https://v2.heppu.ai/api/v1/calls/outbound',
headers={'x-api-key': 'YOUR_API_KEY'},
json={
'agentId': '550e8400-e29b-41d4-a716-446655440000',
'phoneNumber': '+14155552671',
'callerId': '+14155551234',
'priority': 0,
'metadata': {
'customerName': 'John Doe',
'accountId': 'ACC-12345',
'callReason': 'urgent_support'
}
}
)
data = response.json()
print(f"Urgent call initiated: {data['data']}")Call with Error Handling
curl -X POST https://v2.heppu.ai/api/v1/calls/outbound \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agentId": "550e8400-e29b-41d4-a716-446655440000",
"phoneNumber": "+14155552671"
}' \
-w "\nHTTP Status: %{http_code}\n" \
-sasync function initiateOutboundCall(agentId, phoneNumber, options = {}) {
try {
const response = await fetch('https://v2.heppu.ai/api/v1/calls/outbound', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
agentId,
phoneNumber,
...options
})
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error.message);
}
const data = await response.json();
console.log('✓ Call initiated successfully');
console.log(` Room ID: ${data.data.roomId}`);
console.log(` Call ID: ${data.data.id}`);
console.log(` Status: ${data.data.status}`);
return data.data;
} catch (error) {
console.error('✗ Failed to initiate call:', error.message);
throw error;
}
}
// Usage
const call = await initiateOutboundCall(
'550e8400-e29b-41d4-a716-446655440000',
'+14155552671',
{ priority: 0 }
);def initiate_outbound_call(agent_id, phone_number, **kwargs):
try:
response = requests.post(
'https://v2.heppu.ai/api/v1/calls/outbound',
headers={'x-api-key': 'YOUR_API_KEY'},
json={
'agentId': agent_id,
'phoneNumber': phone_number,
**kwargs
},
timeout=10
)
response.raise_for_status()
data = response.json()
print('✓ Call initiated successfully')
print(f" Room ID: {data['data']['roomId']}")
print(f" Call ID: {data['data']['id']}")
print(f" Status: {data['data']['status']}")
return data['data']
except requests.exceptions.HTTPError as e:
error_data = e.response.json()
print(f"✗ Failed to initiate call: {error_data['error']['message']}")
raise
# Usage
call = initiate_outbound_call(
'550e8400-e29b-41d4-a716-446655440000',
'+14155552671',
priority=0
)Response Examples
Success Response (201)
{
"data": {
"id": "call_xyz789abc",
"status": "connecting",
"agentId": "550e8400-e29b-41d4-a716-446655440000",
"phoneNumber": "+14155552671",
"callerId": "+14155551234",
"roomId": "room_lk_abc123def456",
"roomName": "room_lk_abc123def456",
"dispatchId": "dispatch_789xyz",
"sipCallId": "sip_456abc",
"startedAt": "2025-12-01T15:30:00.000Z",
"agent": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Customer Support Agent",
"type": "voice"
},
"metadata": {
"caller_id": "+14155551234",
"from_phone_number": "+14155551234",
"priority": 5,
"customerName": "John Doe",
"accountId": "ACC-12345"
}
},
"message": "Outbound call initiated successfully via LiveKit"
}Error Responses
Validation Error (Zod)
{
"error": {
"message": "Validation error: Phone number must be in E.164 format (+1234567890)",
"code": 400
}
}Invalid Agent ID Format
{
"error": {
"message": "Validation error: Invalid agent ID format",
"code": 400
}
}Agent Not Found
{
"error": {
"message": "Agent not found or access denied",
"code": 404
}
}Agent Not Voice Type
{
"error": {
"message": "Agent must be a voice agent for outbound calls",
"code": 400
}
}Agent Not Active
{
"error": {
"message": "Agent must be active to make calls",
"code": 400
}
}LiveKit Not Configured
{
"error": {
"message": "LiveKit service is not configured. Please contact support.",
"code": 500
}
}Call Initiation Failed
{
"error": {
"message": "Failed to initiate call: Connection timeout",
"code": 500
}
}LiveKit Integration
Call Flow
- Validation - Request is validated using Zod schema
- Agent Verification - Agent exists, is voice type, and is active
- Call Log Creation - Database record created with "initiated" status
- LiveKit Room Creation - LiveKit SDK creates room and SIP trunk
- Call Initiation - SIP call is placed to the contact
- Status Update - Call log updated to "connecting" with LiveKit details
- Response - Room and call details returned to client
LiveKit Details
The response includes LiveKit-specific information:
roomId/roomName: LiveKit room identifier for this calldispatchId: LiveKit dispatch/job ID for trackingsipCallId: SIP participant identifier
Use these IDs to:
- Monitor call status via LiveKit API
- Join the room programmatically
- Track call metrics and events
Use Cases
Click-to-Call from CRM
async function clickToCall(contactId, agentId) {
// Fetch contact from CRM
const contact = await fetchContact(contactId);
// Initiate immediate call
const response = await fetch('https://v2.heppu.ai/api/v1/calls/outbound', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
agentId: agentId,
phoneNumber: contact.phone,
priority: 0, // High priority for manual calls
metadata: {
contactId: contact.id,
contactName: contact.name,
accountId: contact.accountId,
source: 'crm_click_to_call',
initiatedBy: 'sales_rep'
}
})
});
const data = await response.json();
// Update CRM with call details
await updateCRMCall(contactId, {
callId: data.data.id,
roomId: data.data.roomId,
status: 'connecting'
});
return data.data;
}Emergency Call System
def initiate_emergency_call(contact_phone, alert_type, location):
"""Initiate high-priority emergency call"""
response = requests.post(
'https://v2.heppu.ai/api/v1/calls/outbound',
headers={'x-api-key': 'YOUR_API_KEY'},
json={
'agentId': EMERGENCY_AGENT_ID,
'phoneNumber': contact_phone,
'priority': 0, # Highest priority
'metadata': {
'alertType': alert_type,
'location': location,
'timestamp': datetime.utcnow().isoformat(),
'urgency': 'critical',
'requiresImmediate': True
}
},
timeout=5
)
response.raise_for_status()
data = response.json()
# Log emergency call
log_emergency_call(
call_id=data['data']['id'],
room_id=data['data']['roomId'],
alert_type=alert_type,
location=location
)
return data['data']Real-time Call Monitoring
async function initiateAndMonitor(agentId, phoneNumber) {
// Initiate call
const response = await fetch('https://v2.heppu.ai/api/v1/calls/outbound', {
method: 'POST',
headers: {
'x-api-key': 'YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({ agentId, phoneNumber })
});
const { data: call } = await response.json();
console.log(`Call initiated: ${call.id}`);
// Monitor call status
const checkInterval = setInterval(async () => {
const statusResponse = await fetch(
`https://v2.heppu.ai/api/v1/calls/${call.id}`,
{ headers: { 'x-api-key': 'YOUR_API_KEY' } }
);
const { data: currentCall } = await statusResponse.json();
console.log(`Status: ${currentCall.status}`);
// Stop monitoring when call ends
if (['completed', 'failed', 'cancelled'].includes(currentCall.status)) {
clearInterval(checkInterval);
console.log('Call ended');
console.log(`Duration: ${currentCall.duration} seconds`);
console.log(`Summary: ${currentCall.summary}`);
}
}, 5000); // Check every 5 seconds
return call;
}Webhook Triggered Call
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhook/trigger-call', methods=['POST'])
def webhook_trigger_call():
"""Webhook endpoint to trigger outbound calls"""
data = request.json
# Validate webhook payload
if not data.get('phoneNumber') or not data.get('agentId'):
return jsonify({'error': 'Missing required fields'}), 400
try:
# Initiate call via Heppu API
response = requests.post(
'https://v2.heppu.ai/api/v1/calls/outbound',
headers={'x-api-key': 'YOUR_API_KEY'},
json={
'agentId': data['agentId'],
'phoneNumber': data['phoneNumber'],
'priority': data.get('priority', 5),
'metadata': {
'source': 'webhook',
'webhookId': data.get('id'),
**data.get('metadata', {})
}
},
timeout=10
)
response.raise_for_status()
call_data = response.json()
return jsonify({
'success': True,
'callId': call_data['data']['id'],
'roomId': call_data['data']['roomId']
}), 201
except requests.exceptions.RequestException as e:
return jsonify({
'success': False,
'error': str(e)
}), 500Best Practices
- Use E.164 format - Always validate phone numbers before calling
- Handle agent status - Verify agent is active before initiating calls
- Set appropriate priority - Use priority 0 for urgent calls
- Include metadata - Store context for better call handling
- Implement monitoring - Poll call status for real-time updates
- Error handling - Always handle validation and API errors
- Rate limiting - Respect API rate limits for outbound calls
- Timeout handling - Set reasonable timeouts for API requests
Differences from Schedule Call
| Feature | Outbound Call | Schedule Call |
|---|---|---|
| Execution | Immediate via LiveKit | Queued for processing |
| Use Case | Real-time, manual calls | Automated, bulk calls |
| Response | LiveKit room details | Call scheduling confirmation |
| Status | "connecting" | "initiated" or "scheduled" |
| Infrastructure | LiveKit SDK | Queue system |
Related Endpoints
- Schedule Call - Schedule a call for later
- Batch Schedule - Schedule multiple calls
- Get Call Details - Monitor call status
- Cancel Call - Cancel an active call