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
/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
/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
/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
/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."
}