Heppu AI
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/outbound

Authentication

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

FieldTypeDescription
agentIdstring (UUID)ID of the voice agent to use
phoneNumberstringContact phone number in E.164 format

Optional Fields

FieldTypeDefaultDescription
callerIdstringAgent's first phoneSpecific phone number to display as caller ID
metadataobjectCustom metadata for the call
priorityinteger (0-10)5Call 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 identifier
  • status: Always "connecting" for newly initiated calls
  • agentId: Agent UUID
  • phoneNumber: Contact's phone number
  • callerId: Displayed caller ID
  • roomId: LiveKit room identifier
  • roomName: LiveKit room name (same as roomId)
  • dispatchId: LiveKit dispatch/job ID
  • sipCallId: SIP participant identifier
  • startedAt: Call initiation timestamp (ISO 8601)
  • agent: Agent information object
  • metadata: 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" \
  -s
async 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

  1. Validation - Request is validated using Zod schema
  2. Agent Verification - Agent exists, is voice type, and is active
  3. Call Log Creation - Database record created with "initiated" status
  4. LiveKit Room Creation - LiveKit SDK creates room and SIP trunk
  5. Call Initiation - SIP call is placed to the contact
  6. Status Update - Call log updated to "connecting" with LiveKit details
  7. Response - Room and call details returned to client

LiveKit Details

The response includes LiveKit-specific information:

  • roomId / roomName: LiveKit room identifier for this call
  • dispatchId: LiveKit dispatch/job ID for tracking
  • sipCallId: 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)
        }), 500

Best Practices

  1. Use E.164 format - Always validate phone numbers before calling
  2. Handle agent status - Verify agent is active before initiating calls
  3. Set appropriate priority - Use priority 0 for urgent calls
  4. Include metadata - Store context for better call handling
  5. Implement monitoring - Poll call status for real-time updates
  6. Error handling - Always handle validation and API errors
  7. Rate limiting - Respect API rate limits for outbound calls
  8. Timeout handling - Set reasonable timeouts for API requests

Differences from Schedule Call

FeatureOutbound CallSchedule Call
ExecutionImmediate via LiveKitQueued for processing
Use CaseReal-time, manual callsAutomated, bulk calls
ResponseLiveKit room detailsCall scheduling confirmation
Status"connecting""initiated" or "scheduled"
InfrastructureLiveKit SDKQueue system

On this page