Calls API

The Calls API allows you to programmatically create outbound calls using your AI voice agents and retrieve call details and analytics.

Base URL: https://kalem.me/api/v1/calls

The Call Object

{
  "call_id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "completed",
  "to_number": "+15551234567",
  "from_number": "+15559876543",
  "agent": "Customer Support Agent",
  "duration_seconds": 245,
  "duration_minutes": 4.08,
  "cost": 0.98,
  "realtime_cost": 0.82,
  "started_at": "2024-12-10T14:30:00+00:00",
  "answered_at": "2024-12-10T14:30:03+00:00",
  "ended_at": "2024-12-10T14:34:08+00:00",
  "recording_url": "https://storage.kalem.me/recordings/550e8400.mp3",
  "hangup_cause": "NORMAL_CLEARING"
}

Fields

Field Type Description
call_id string (UUID) Unique identifier for the call
status string Current call status: pending, ringing, in_progress, completed, failed, no_answer, busy
to_number string Destination phone number
from_number string Your phone number used for the call
agent string Name of the AI agent handling the call
duration_seconds integer Call duration in seconds (null if not answered)
duration_minutes float Call duration in minutes (null if not answered)
cost float Total cost in USD (null if not completed)
realtime_cost float Real-time AI processing cost in USD (null if not completed)
started_at string (ISO 8601) When the call was initiated (null if not started)
answered_at string (ISO 8601) When the call was answered (null if not answered)
ended_at string (ISO 8601) When the call ended (null if still in progress)
recording_url string (URL) URL to download call recording (null if recording disabled or not available)
hangup_cause string Reason for call termination (null if still in progress)

List Calls

GET /api/v1/calls

Retrieve a paginated list of calls for your account. Supports filtering by agent, number, status, direction, and date range.

Query Parameters

Parameter Type Required Description
agent_id integer Optional Filter calls by agent ID
number_id integer Optional Filter calls by number ID
status string Optional Filter by status: pending, ringing, in-progress, completed, failed, cancelled
direction string Optional Filter by direction: inbound, outbound
date_from string Optional Filter calls from this date (ISO 8601)
date_to string Optional Filter calls up to this date (ISO 8601)
search string Optional Search by phone number or call ID
per_page integer Optional Number of results per page (default: 15, max: 100)

Example Request

curl -X GET "https://kalem.me/api/v1/calls?status=completed&per_page=25" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Accept: application/json"
const response = await fetch(
  'https://kalem.me/api/v1/calls?status=completed&per_page=25',
  {
    headers: {
      'Authorization': 'Bearer YOUR_API_TOKEN',
      'Accept': 'application/json',
    },
  }
);
const data = await response.json();
import requests

response = requests.get(
    'https://kalem.me/api/v1/calls',
    params={'status': 'completed', 'per_page': 25},
    headers={'Authorization': 'Bearer YOUR_API_TOKEN'}
)
data = response.json()
$response = Http::withToken('YOUR_API_TOKEN')
    ->get('https://kalem.me/api/v1/calls', [
        'status' => 'completed',
        'per_page' => 25,
    ]);

$data = $response->json();

Create Call

POST /api/v1/calls

Initiates an outbound call to one or more phone numbers using your configured AI agent and phone number. You can create a single call by providing a string, or multiple calls simultaneously by providing an array of phone numbers.

Multiple Calls: When you provide an array of phone numbers, a separate call will be created for each number with a unique call_id. Each call will use the same agent, number, and trunk configuration.

Trunk Requirement: The trunk_id is required when calling E.164 phone numbers (e.g., +15551234567) but optional for SIP URIs (e.g., sip:1001@sip.kalem.me).

Phone Number Formatting: E.164 phone numbers can be provided in various formats like "+1 (863) 990-1708", "+1-555-123-4567", or "+15551234567". Spaces, hyphens, and parentheses are automatically cleaned before saving.

Request Body Parameters

Parameter Type Required Description
to_number string or array Required Destination phone number(s) in E.164 format (e.g., "+15551234567") or SIP URI format (e.g., "sip:1001@sip.kalem.me"). Can be a single string or an array for multiple calls
agent_id integer Required ID of the AI agent to use for the call(s)
number_id integer Required ID of the phone number to call from (must be active)
trunk_id integer Required for E.164 Optional for SIP ID of the SIP trunk to use (must be active). Required when calling E.164 phone numbers, optional for SIP URIs
record boolean Optional Whether to record the call(s) (default: true)
metadata object Optional Custom metadata to attach to the call(s)
Post-Call Webhook (optional overrides)
post_call_webhook_url string Optional Override the agent's post-call webhook URL for this call. Max 2048 characters.
post_call_webhook_method string Optional HTTP method for the post-call webhook. Allowed: POST, GET, PUT, PATCH. Defaults to POST.
post_call_webhook_headers array Optional Custom HTTP headers for the post-call webhook. Array of objects with key and value properties.
Call Status Webhook (optional overrides)
call_status_webhook_url string Optional Override the agent's call-status webhook URL. Fires on every status change. Max 2048 characters.
call_status_webhook_method string Optional HTTP method for the call-status webhook. Allowed: POST, PUT, PATCH. Defaults to POST.
call_status_webhook_headers array Optional Custom HTTP headers for the call-status webhook. Array of objects with key and value properties.

Webhook Priority: When webhook fields are provided on a call, they override the agent-level webhook configuration for that specific call.

Post-Call vs Call Status: The post-call webhook fires once when a call ends with full call data (transcriptions, summary, costs, recording). The call-status webhook fires on every status change (initiated → ringing → in-progress → completed) with a lightweight payload.

Example Requests

curl -X POST https://kalem.me/api/v1/calls \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -d '{
    "to_number": "+15551234567",
    "agent_id": 1,
    "number_id": 1,
    "trunk_id": 1,
    "record": true
  }'
curl -X POST https://kalem.me/api/v1/calls \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -d '{
    "to_number": ["+15551234567", "+15559876543", "+15558889999"],
    "agent_id": 1,
    "number_id": 1,
    "trunk_id": 1,
    "record": true
  }'
curl -X POST https://kalem.me/api/v1/calls \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -d '{
    "to_number": "sip:1001@sip.kalem.me",
    "agent_id": 1,
    "number_id": 1,
    "record": true
  }'
curl -X POST https://kalem.me/api/v1/calls \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -d '{
    "to_number": "+15551234567",
    "agent_id": 1,
    "number_id": 1,
    "trunk_id": 1,
    "record": true,
    "post_call_webhook_url": "https://your-server.com/api/webhooks/call-ended",
    "post_call_webhook_method": "POST",
    "post_call_webhook_headers": [
      {"key": "Authorization", "value": "Bearer your-token"},
      {"key": "X-Custom-Header", "value": "custom-value"}
    ],
    "call_status_webhook_url": "https://your-server.com/api/webhooks/call-status",
    "call_status_webhook_method": "POST",
    "call_status_webhook_headers": [
      {"key": "Authorization", "value": "Bearer your-token"}
    ]
  }'
// Single Call
const response = await fetch('https://kalem.me/api/v1/calls', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_API_TOKEN'
  },
  body: JSON.stringify({
    to_number: '+15551234567',
    agent_id: 1,
    number_id: 1,
    trunk_id: 1,
    record: true
  })
});

const data = await response.json();

// Multiple Calls
const multiResponse = await fetch('https://kalem.me/api/v1/calls', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_API_TOKEN'
  },
  body: JSON.stringify({
    to_number: ['+15551234567', '+15559876543'],
    agent_id: 1,
    number_id: 1,
    trunk_id: 1,
    record: true
  })
});

const multiData = await multiResponse.json();
import requests

url = 'https://kalem.me/api/v1/calls'
headers = {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_API_TOKEN'
}

# Single Call
response = requests.post(url, json={
    'to_number': '+15551234567',
    'agent_id': 1,
    'number_id': 1,
    'trunk_id': 1,
    'record': True
}, headers=headers)

# Multiple Calls
multi_response = requests.post(url, json={
    'to_number': ['+15551234567', '+15559876543'],
    'agent_id': 1,
    'number_id': 1,
    'trunk_id': 1,
    'record': True
}, headers=headers)
// Single Call
$response = Http::withToken('YOUR_API_TOKEN')
    ->post('https://kalem.me/api/v1/calls', [
        'to_number' => '+15551234567',
        'agent_id' => 1,
        'number_id' => 1,
        'trunk_id' => 1,
        'record' => true,
    ]);

// Multiple Calls
$response = Http::withToken('YOUR_API_TOKEN')
    ->post('https://kalem.me/api/v1/calls', [
        'to_number' => ['+15551234567', '+15559876543'],
        'agent_id' => 1,
        'number_id' => 1,
        'trunk_id' => 1,
        'record' => true,
    ]);

Success Response - Single Call (201 Created)

{
  "message": "Call initiated successfully",
  "data": {
    "call_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "pending",
    "to_number": "+15551234567",
    "from_number": "+15559876543",
    "agent": "Customer Support Agent"
  }
}

Success Response - Multiple Calls (201 Created)

{
  "message": "3 calls initiated successfully",
  "data": [
    {
      "call_id": "550e8400-e29b-41d4-a716-446655440001",
      "status": "pending",
      "to_number": "+15551234567",
      "from_number": "+15559876543",
      "agent": "Customer Support Agent"
    },
    {
      "call_id": "550e8400-e29b-41d4-a716-446655440002",
      "status": "pending",
      "to_number": "+15559876543",
      "from_number": "+15559876543",
      "agent": "Customer Support Agent"
    },
    {
      "call_id": "550e8400-e29b-41d4-a716-446655440003",
      "status": "pending",
      "to_number": "+15558889999",
      "from_number": "+15559876543",
      "agent": "Customer Support Agent"
    }
  ]
}

Error Responses

422 Unprocessable Entity - Invalid phone number format

{
  "message": "Each phone number must be in E.164 format or SIP URI format.",
  "errors": {
    "to_number.0": [
      "Each phone number must be in E.164 format (e.g., +15551234567) or SIP URI format (e.g., sip:1001@sip.kalem.me)."
    ]
  }
}

422 Unprocessable Entity - Missing trunk for E.164 number

{
  "message": "A trunk is required when calling E.164 phone numbers.",
  "errors": {
    "trunk_id": [
      "A trunk is required when calling E.164 phone numbers."
    ]
  }
}

Get Call

GET /api/v1/calls/{callId}

Retrieve detailed information about a specific call, including duration, cost, status, and recording URL.

Example Request

curl -X GET https://kalem.me/api/v1/calls/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const callId = '550e8400-e29b-41d4-a716-446655440000';
const response = await fetch(`https://kalem.me/api/v1/calls/${callId}`, {
  headers: { 'Authorization': 'Bearer YOUR_API_TOKEN' }
});
const data = await response.json();
import requests

call_id = '550e8400-e29b-41d4-a716-446655440000'
response = requests.get(
    f'https://kalem.me/api/v1/calls/{call_id}',
    headers={'Authorization': 'Bearer YOUR_API_TOKEN'}
)
data = response.json()
$callId = '550e8400-e29b-41d4-a716-446655440000';
$response = Http::withToken('YOUR_API_TOKEN')
    ->get("https://kalem.me/api/v1/calls/{$callId}");

$data = $response->json();

Success Response (200 OK)

{
  "data": {
    "call_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "completed",
    "to_number": "+15551234567",
    "from_number": "+15559876543",
    "agent": "Customer Support Agent",
    "duration_seconds": 245,
    "duration_minutes": 4.08,
    "cost": 0.98,
    "realtime_cost": 0.82,
    "started_at": "2024-12-10T14:30:00+00:00",
    "answered_at": "2024-12-10T14:30:03+00:00",
    "ended_at": "2024-12-10T14:34:08+00:00",
    "recording_url": "https://storage.kalem.me/recordings/550e8400.mp3",
    "hangup_cause": "NORMAL_CLEARING"
  }
}

Delete Call

DELETE /api/v1/calls/{callId}

Delete a completed or failed call record. Active calls (pending, ringing, in-progress) cannot be deleted.

Example Request

curl -X DELETE https://kalem.me/api/v1/calls/550e8400-e29b-41d4-a716-446655440000 \
  -H "Authorization: Bearer YOUR_API_TOKEN"

Success Response (200 OK)

{
  "message": "Call deleted successfully."
}

Error Response (Active Call)

{
  "message": "Cannot delete an active call. Wait for the call to complete or fail."
}