> ## Documentation Index
> Fetch the complete documentation index at: https://docs.teli.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# List Campaign Calls

> Get all call records for a voice campaign

## 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`](/partner-api/call-history/list) so a single client implementation works for both endpoints.

## Authentication

<ParamField header="X-API-Key" type="string" required>
  Your Teli API key
</ParamField>

## Path Parameters

<ParamField path="campaign_id" type="string" required>
  The campaign identifier
</ParamField>

## Query Parameters

<ParamField query="status" type="string" default="all">
  Filter by call outcome: `connected`, `failed`, `voicemail`, `all`
</ParamField>

<ParamField query="sentiment" type="string">
  Filter by sentiment: `Positive`, `Negative`, `Neutral`, `Unknown`
</ParamField>

<ParamField query="limit" type="integer" default="100">
  Page size — maximum results to return per request (max: 500)
</ParamField>

<ParamField query="pagination_key" type="string">
  Cursor for fetching the next page. Pass the `next_pagination_key` value from the previous response. Omit on the first request.
</ParamField>

## Response Fields

<ResponseField name="success" type="boolean">
  Whether the request was successful
</ResponseField>

<ResponseField name="campaign_id" type="string">
  Campaign identifier
</ResponseField>

<ResponseField name="count" type="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.
</ResponseField>

<ResponseField name="has_more" type="boolean">
  `true` when more calls exist beyond this page. Continue requesting pages until this is `false`.
</ResponseField>

<ResponseField name="next_pagination_key" type="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.
</ResponseField>

<ResponseField name="calls" type="array">
  Array of call records

  <Expandable title="Call Object">
    <ResponseField name="call_id" type="string">
      Unique call identifier
    </ResponseField>

    <ResponseField name="to_number" type="string">
      Phone number called (E.164 format)
    </ResponseField>

    <ResponseField name="from_number" type="string">
      Outbound phone number used
    </ResponseField>

    <ResponseField name="call_successful" type="boolean">
      Whether the call achieved its objective
    </ResponseField>

    <ResponseField name="in_voicemail" type="boolean">
      Whether the call went to voicemail
    </ResponseField>

    <ResponseField name="call_status" type="string">
      Call status (e.g., `"ended"`)
    </ResponseField>

    <ResponseField name="user_sentiment" type="string">
      Detected sentiment: `Positive`, `Negative`, `Neutral`, `Unknown`
    </ResponseField>

    <ResponseField name="call_duration_ms" type="integer">
      Call duration in milliseconds
    </ResponseField>

    <ResponseField name="start_timestamp" type="string">
      Call start time (ISO 8601 UTC)
    </ResponseField>

    <ResponseField name="end_timestamp" type="string">
      Call end time (ISO 8601 UTC)
    </ResponseField>

    <ResponseField name="transcript" type="string">
      Full call transcript
    </ResponseField>

    <ResponseField name="extracted_fields" type="object">
      Data extracted from conversation (based on agent configuration)
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="powered_by" type="string">
  Always returns "Teli"
</ResponseField>

## Example Request

```bash First page theme={null}
curl -X GET "https://api.teli.ai/v1/voice/campaigns/voice_campaign_abc123/calls?limit=500" \
  -H "X-API-Key: YOUR_API_KEY"
```

```bash Next page (using cursor from previous response) theme={null}
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

```json 200 theme={null}
{
  "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 Python theme={null}
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 JavaScript theme={null}
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 Value | Returns Calls Where                               |
| ------------ | ------------------------------------------------- |
| `connected`  | `call_duration_ms > 0` AND `in_voicemail = false` |
| `voicemail`  | `in_voicemail = true`                             |
| `failed`     | `call_duration_ms = 0` AND `in_voicemail = false` |
| `all`        | All calls (default)                               |

## Fields NOT Included

| Field                  | Reason                                                                             |
| ---------------------- | ---------------------------------------------------------------------------------- |
| `disconnection_reason` | Use `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

## Related Endpoints

* [Get Analytics](/partner-api/voice-campaigns/analytics) - Aggregate statistics
* [Failed Numbers](/partner-api/voice-campaigns/failed-numbers) - Just failed phone numbers
* [Call History](/partner-api/call-history/list) - Cross-campaign call history
