Skip to main content
GET
/
v1
/
voice
/
campaigns
/
{campaign_id}
/
calls
List Campaign Calls
curl --request GET \
  --url https://api.example.com/v1/voice/campaigns/{campaign_id}/calls \
  --header 'X-API-Key: <x-api-key>'
{
  "success": true,
  "campaign_id": "<string>",
  "count": 123,
  "has_more": true,
  "next_pagination_key": {},
  "calls": [
    {
      "call_id": "<string>",
      "to_number": "<string>",
      "from_number": "<string>",
      "call_successful": true,
      "in_voicemail": true,
      "call_status": "<string>",
      "user_sentiment": "<string>",
      "call_duration_ms": 123,
      "start_timestamp": "<string>",
      "end_timestamp": "<string>",
      "transcript": "<string>",
      "extracted_fields": {}
    }
  ],
  "powered_by": "<string>"
}

Description

Returns individual call records for a campaign with full details including transcripts, sentiment, and extracted data. Cursor-paginated — for campaigns with more than limit calls, follow the next_pagination_key cursor returned in the response to walk through every page. The pagination contract mirrors GET /v1/voice/calls so a single client implementation works for both endpoints.

Authentication

X-API-Key
string
required
Your Teli API key

Path Parameters

campaign_id
string
required
The campaign identifier

Query Parameters

status
string
default:"all"
Filter by call outcome: connected, failed, voicemail, all
sentiment
string
Filter by sentiment: Positive, Negative, Neutral, Unknown
limit
integer
default:"100"
Page size — maximum results to return per request (max: 500)
pagination_key
string
Cursor for fetching the next page. Pass the next_pagination_key value from the previous response. Omit on the first request.

Response Fields

success
boolean
Whether the request was successful
campaign_id
string
Campaign identifier
count
integer
Number of calls in this page (after any status/sentiment filters were applied). May be less than limit when filtering — keep paging on has_more, not page size.
has_more
boolean
true when more calls exist beyond this page. Continue requesting pages until this is false.
next_pagination_key
string | null
Cursor to fetch the next page. Pass this value as the pagination_key query param on the next request. null when there are no more pages.
calls
array
Array of call records
powered_by
string
Always returns “Teli”

Example Request

First page
curl -X GET "https://api.teli.ai/v1/voice/campaigns/voice_campaign_abc123/calls?limit=500" \
  -H "X-API-Key: YOUR_API_KEY"
Next page (using cursor from previous response)
curl -X GET "https://api.teli.ai/v1/voice/campaigns/voice_campaign_abc123/calls?limit=500&pagination_key=1738080000000" \
  -H "X-API-Key: YOUR_API_KEY"

Example Response

200
{
  "success": true,
  "campaign_id": "voice_campaign_abc123",
  "calls": [
    {
      "call_id": "call_xyz789",
      "to_number": "+15559876543",
      "from_number": "+15551234567",
      "call_successful": true,
      "in_voicemail": false,
      "call_status": "ended",
      "user_sentiment": "Positive",
      "call_duration_ms": 45000,
      "start_timestamp": "2026-01-28T10:30:00.000000",
      "end_timestamp": "2026-01-28T10:30:45.000000",
      "transcript": "Agent: Hi John, how are you today?\nUser: I'm doing well, thanks for calling...",
      "extracted_fields": {
        "interest_level": "high",
        "callback_requested": true
      }
    }
  ],
  "count": 1,
  "has_more": true,
  "next_pagination_key": "1738080000000",
  "powered_by": "Teli"
}

Paginating Through All Calls

For campaigns with more calls than limit, loop on has_more until the server signals exhaustion:
Python
def list_all_calls(campaign_id, api_key, page_size=500):
    calls = []
    cursor = None
    while True:
        params = {"limit": page_size}
        if cursor:
            params["pagination_key"] = cursor
        r = requests.get(
            f"https://api.teli.ai/v1/voice/campaigns/{campaign_id}/calls",
            headers={"X-API-Key": api_key},
            params=params,
        ).json()
        calls.extend(r["calls"])
        if not r.get("has_more"):
            break
        cursor = r["next_pagination_key"]
    return calls
JavaScript
async function listAllCalls(campaignId, apiKey, pageSize = 500) {
  const calls = [];
  let cursor = null;
  while (true) {
    const params = new URLSearchParams({ limit: String(pageSize) });
    if (cursor) params.set("pagination_key", cursor);
    const r = await fetch(
      `https://api.teli.ai/v1/voice/campaigns/${campaignId}/calls?${params}`,
      { headers: { "X-API-Key": apiKey } }
    ).then(r => r.json());
    calls.push(...r.calls);
    if (!r.has_more) break;
    cursor = r.next_pagination_key;
  }
  return calls;
}

Status Filter Logic

Status ValueReturns Calls Where
connectedcall_duration_ms > 0 AND in_voicemail = false
voicemailin_voicemail = true
failedcall_duration_ms = 0 AND in_voicemail = false
allAll calls (default)

Fields NOT Included

FieldReason
disconnection_reasonUse call_successful, in_voicemail, and call_duration_ms to determine outcome

Notes

  • Calls are ordered by start_timestamp (newest first)
  • The cursor (next_pagination_key) is the start_timestamp of the last call in the current page, used as a < bound on the next request
  • Server-side status / sentiment filters are applied AFTER pagination slicing. This means a page may contain fewer than limit items even when has_more=true — always loop on has_more, never on the returned page size
  • A null or missing next_pagination_key means you’ve reached the end (combined with has_more: false)
  • Calling with the same pagination_key twice is idempotent — returns the same page
  • Transcripts may be large; lower limit if you hit response size limits
  • extracted_fields depends on your agent’s extraction configuration