Skip to main content
GET
/
v1
/
voice
/
calls
Export Call History
curl --request GET \
  --url https://api.example.com/v1/voice/calls \
  --header 'X-API-Key: <x-api-key>'

Description

Exports call history as a downloadable file. Uses the same GET /v1/voice/calls endpoint as List Call History, but switches to export mode when the format query parameter is set to csv, xlsx, or json. Exports paginate through the full result set server-side (no 1000 row cap) and apply every filter supported by the list endpoint.
  • CSV — streamed row-by-row, safe for very large exports.
  • XLSX — buffered Excel workbook (.xlsx), good for most business reports.
  • JSON — single JSON document with a calls array; use for programmatic consumption.

Safety Cap

Exports are capped at 50,000 rows per call. When the cap is hit, the response still returns 200 with the first 50,000 matching rows and the X-Export-Truncated response header set to true. Narrow the time window or add filters to page through larger result sets.

Authentication

X-API-Key
string
required
Your Teli API key

Query Parameters

Export Control

format
string
required
Export format. One of: csv, xlsx, json. Omit to use the standard paginated JSON list response instead.
columns
string
Comma-separated list of column ids to include, in order. Omit to use the default set.Valid ids: call_id, start_time, end_time, duration_seconds, from_number, to_number, direction, agent_name, user_sentiment, call_successful, in_voicemail, call_status, recording_url, campaign_id, contact_id, transcript.Default: call_id, start_time, duration_seconds, from_number, to_number, direction, agent_name, user_sentiment, call_successful, in_voicemail, recording_url, campaign_id
include_extracted_fields
boolean
default:"true"
When true, each distinct key observed across all exported calls’ extracted_fields becomes its own column, named extracted.{key} (CSV/XLSX) or grouped under an extracted_fields object (JSON). Set to false to omit extracted data entirely.

Scope

organization_id
string
required
Organization unique_id to filter calls
user_id
string
User unique_id (required for non-admin users)
is_admin
boolean
default:"false"
true returns all organization calls; false restricts to the caller’s own calls

Filters (identical to the list endpoint)

agent_id
string
Filter by agent (unique_id or internal UUID)
call_status
string
default:"ended"
ended, ongoing, or error
direction
string
inbound or outbound
sentiment
string
Positive, Negative, Neutral, or Unknown
call_successful
boolean
true or false
in_voicemail
boolean
true or false
has_recording
boolean
true to include only calls with a recording URL; false for calls without
campaign_id
string
Filter by campaign UUID
phone_contains
string
Substring match against from_number OR to_number. The leading + is stripped, so 4155551234 and +4155551234 behave the same.
duration_min_ms
integer
Minimum call_duration_ms (inclusive)
duration_max_ms
integer
Maximum call_duration_ms (inclusive)
start_date
string
ISO-8601 lower bound on call start time (e.g., 2026-04-01T00:00:00Z)
end_date
string
ISO-8601 upper bound on call start time

Response Headers

Every export response includes these headers:
HeaderDescription
Content-Typetext/csv; charset=utf-8, the XLSX MIME type, or application/json; charset=utf-8
Content-Dispositionattachment; filename="call-history-YYYYMMDD-HHMMSS.<ext>"
X-Export-Row-CountNumber of rows included in the body
X-Export-Row-CapThe server-side safety cap (currently 50000)
X-Export-Truncatedtrue when the cap was reached, otherwise false

Example Requests

CSV export (defaults)

cURL
curl -X GET "https://api.teli.ai/v1/voice/calls?format=csv&organization_id=ORG_ID&is_admin=true&start_date=2026-04-01T00:00:00Z&end_date=2026-04-22T23:59:59Z" \
  -H "X-API-Key: YOUR_API_KEY" \
  -o call-history.csv

XLSX export with custom columns

cURL
curl -X GET "https://api.teli.ai/v1/voice/calls?format=xlsx&organization_id=ORG_ID&is_admin=true&columns=call_id,start_time,from_number,to_number,duration_seconds,user_sentiment,transcript&include_extracted_fields=true" \
  -H "X-API-Key: YOUR_API_KEY" \
  -o call-history.xlsx

JSON export with filters

cURL
curl -X GET "https://api.teli.ai/v1/voice/calls?format=json&organization_id=ORG_ID&is_admin=true&direction=outbound&sentiment=Positive&has_recording=true" \
  -H "X-API-Key: YOUR_API_KEY" \
  -o call-history.json
JavaScript
const params = new URLSearchParams({
  format: 'csv',
  organization_id: orgId,
  is_admin: 'true',
  start_date: '2026-04-01T00:00:00Z',
  end_date: '2026-04-22T23:59:59Z',
  columns: 'call_id,start_time,from_number,to_number,duration_seconds,user_sentiment'
});

const response = await fetch(`https://api.teli.ai/v1/voice/calls?${params}`, {
  headers: { 'X-API-Key': 'YOUR_API_KEY' }
});

const truncated = response.headers.get('X-Export-Truncated') === 'true';
const rowCount = response.headers.get('X-Export-Row-Count');

const blob = await response.blob();
// ...save or display the file
Python
import requests

params = {
    "format": "xlsx",
    "organization_id": org_id,
    "is_admin": "true",
    "start_date": "2026-04-01T00:00:00Z",
    "end_date": "2026-04-22T23:59:59Z",
    "include_extracted_fields": "true",
}

response = requests.get(
    "https://api.teli.ai/v1/voice/calls",
    params=params,
    headers={"X-API-Key": "YOUR_API_KEY"},
    stream=True,
)

with open("call-history.xlsx", "wb") as f:
    for chunk in response.iter_content(chunk_size=65536):
        f.write(chunk)

print("Rows:", response.headers.get("X-Export-Row-Count"))
print("Truncated:", response.headers.get("X-Export-Truncated"))

Example Responses

CSV body (abbreviated)

Call ID,Start Time (UTC),Duration (s),From,To,Direction,Agent,Sentiment,Successful,Voicemail,Recording URL,Campaign ID,extracted.first_name,extracted.budget
call_d35fab7c6bd8cf9aee6c322b7a4,2026-04-15T19:45:03Z,125,+15174686941,+15551234567,outbound,Sales Agent,Positive,true,false,https://...,camp_123,Jane,250000
call_9a2b0...,2026-04-15T19:52:11Z,42,+15174686941,+15555550104,outbound,Sales Agent,Neutral,false,true,,camp_123,,

JSON body

{
  "count": 2,
  "truncated": false,
  "calls": [
    {
      "call_id": "call_d35fab7c6bd8cf9aee6c322b7a4",
      "start_time": "2026-04-15T19:45:03Z",
      "duration_seconds": 125,
      "from_number": "+15174686941",
      "to_number": "+15551234567",
      "direction": "outbound",
      "agent_name": "Sales Agent",
      "user_sentiment": "Positive",
      "call_successful": true,
      "in_voicemail": false,
      "recording_url": "https://teli-voice-recordings-prod.s3.us-west-2.amazonaws.com/recordings/call_d35fab7c6bd8cf9aee6c322b7a4.wav",
      "campaign_id": "camp_123",
      "extracted_fields": {
        "first_name": "Jane",
        "budget": "250000"
      }
    },
    {
      "call_id": "call_9a2b0...",
      "start_time": "2026-04-15T19:52:11Z",
      "duration_seconds": 42,
      "from_number": "+15174686941",
      "to_number": "+15555550104",
      "direction": "outbound",
      "agent_name": "Sales Agent",
      "user_sentiment": "Neutral",
      "call_successful": false,
      "in_voicemail": true,
      "recording_url": null,
      "campaign_id": "camp_123",
      "extracted_fields": {}
    }
  ]
}

Missing organization_id

400
{
  "error": "organization_id is required",
  "success": false,
  "powered_by": "Teli"
}

Organization not found

404
{
  "error": "Organization not found",
  "success": false,
  "powered_by": "Teli"
}

Behavior

  • Filter parity — every filter accepted by GET /v1/voice/calls also applies to the export. Exports and the paginated list view use the same query contract.
  • No row cap surprise — exports paginate through Teli’s database server-side in 500-row pages. The only limit is the 50,000-row safety cap per request.
  • Tenant- and org-scoped — only calls belonging to the calling tenant and the organization_id you specify are included. Non-admin callers are further restricted to their own user_id.
  • Phantom-call filtering — zero-duration fork artifacts are excluded from exports, matching the list endpoint.
  • Dynamic extracted columns — when include_extracted_fields=true, the exporter scans every exported row and emits one column per distinct extracted_fields key observed across the full result set.
  • Transcripts — opt in by adding transcript to the columns list. Transcripts can be long and will increase file size substantially.

Pagination for Large Exports

If X-Export-Truncated is true, you have more than 50,000 matching calls for the given filters. Paginate by narrowing the date range:
Pass 1: start_date=2026-04-01&end_date=2026-04-07T23:59:59Z
Pass 2: start_date=2026-04-08&end_date=2026-04-14T23:59:59Z
Pass 3: start_date=2026-04-15&end_date=2026-04-21T23:59:59Z
  • GET /v1/voice/calls — paginated JSON list of calls (same filters)
  • GET /v1/voice/calls/{call_id} — full call detail, including transcript
  • GET /v1/voice/calls/{call_id}/recording — signed recording URL
  • GET /v1/voice/calls/{call_id}/extractions — structured extraction for one call