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

# Results and analytics

> Track campaign progress and outcomes.

Every campaign exposes a live stats endpoint and links each call attempt back to a conversation. Use the stats to watch progress in aggregate, and use the per-contact attempts to drill into individual transcripts and recordings.

## Campaign stats

Fetch a snapshot of contact and attempt counts for a campaign.

```bash theme={null}
curl https://api.anyreach.ai/campaign/campaigns/{campaign_id}/stats \
  -H "Authorization: Bearer <token>" \
  -H "X-Anyreach-Org: <organization_id>"
```

Requires the `campaigns:read` or `campaigns:manage` scope. The `X-Anyreach-Org` header is only needed for user PATs (`pat_`); org API keys (`ak_`) carry their org implicitly.

The response groups contacts by lifecycle bucket and reports attempt totals plus current concurrency:

```json theme={null}
{
  "campaign_id": "...",
  "status": "active",
  "contacts": {
    "total": 1240,
    "pending": 310,
    "in_progress": 8,
    "completed": 902,
    "exhausted": 20
  },
  "attempts": {
    "total": 1730,
    "average_per_contact": 1.39
  },
  "active_calls": 8,
  "concurrency_limit": 10
}
```

### Response fields

| Field                          | Type    | Description                                                                                                          |
| ------------------------------ | ------- | -------------------------------------------------------------------------------------------------------------------- |
| `campaign_id`                  | string  | The campaign this snapshot belongs to.                                                                               |
| `status`                       | string  | Current campaign status (for example `active`).                                                                      |
| `contacts.total`               | integer | Sum of all contacts across buckets.                                                                                  |
| `contacts.pending`             | integer | Contacts not yet attempted.                                                                                          |
| `contacts.in_progress`         | integer | Contacts currently being dialed (includes locked and in-progress contacts).                                          |
| `contacts.completed`           | integer | Contacts that reached a terminal completed state.                                                                    |
| `contacts.exhausted`           | integer | Contacts that ran out of retry attempts.                                                                             |
| `attempts.total`               | integer | Total call attempts made across all contacts.                                                                        |
| `attempts.average_per_contact` | number  | `attempts.total` divided by `contacts.total`, rounded to two decimals. `0.0` when there are no contacts or attempts. |
| `active_calls`                 | integer | Calls in flight at the moment of the request.                                                                        |
| `concurrency_limit`            | integer | The org-level cap on simultaneous calls. Defaults to `10` when no org setting exists.                                |

<Note>
  `active_calls` and `concurrency_limit` together tell you how much headroom the campaign has. When `active_calls` reaches `concurrency_limit`, the dialer waits for a call to finish before starting the next one. See [Dialing and retries](/campaigns/dialing-and-retries).
</Note>

### Live polling

The campaign **Overview** tab calls the stats endpoint on load and then polls it every 5 seconds while the campaign `status` is `active` and the browser tab is visible. Polling stops when the tab is hidden or the campaign is no longer active, and resumes when the tab becomes visible again. Use the refresh control to fetch on demand at any time.

## Attempts and conversations

Each call attempt links to a conversation through its `conversation_id`. List a campaign's contacts to see their attempts:

```bash theme={null}
curl "https://api.anyreach.ai/campaign/campaigns/{campaign_id}/contacts?limit=50&offset=0" \
  -H "Authorization: Bearer <token>" \
  -H "X-Anyreach-Org: <organization_id>"
```

`limit` defaults to `50` (max `200`) and `offset` defaults to `0`. Pass `status` to filter contacts by bucket. Each contact carries an `attempts` array, and every attempt includes:

| Field             | Type             | Description                                                        |
| ----------------- | ---------------- | ------------------------------------------------------------------ |
| `id`              | string           | Attempt identifier.                                                |
| `contact_id`      | string           | The contact this attempt belongs to.                               |
| `phone_number`    | string           | The number dialed for this attempt.                                |
| `conversation_id` | string \| null   | The linked conversation, or `null` if no conversation was created. |
| `started_at`      | datetime         | When the attempt started.                                          |
| `ended_at`        | datetime \| null | When the attempt ended, if finished.                               |
| `status`          | string           | The attempt's status.                                              |
| `result`          | object \| null   | Per-conversation evaluation outcomes (see below).                  |

### Conversation outcomes

When an attempt produced a conversation, the contacts list enriches that attempt with its evaluation outcome. For each attempt with a `conversation_id`, the server looks up completed evaluation runs for that conversation and populates the attempt's `result` object, keyed by `<metric_key>.<criterion_key>`. Attempts whose conversation has no completed evaluation, or no conversation at all, leave `result` as `null`.

To review the full transcript and recording for any attempt, open the linked conversation by its `conversation_id` in Conversations.

<CardGroup cols={2}>
  <Card title="Conversations" icon="inbox" href="/conversations/inbox-overview">
    Read transcripts and listen to recordings for every linked conversation.
  </Card>

  <Card title="Dialing and retries" icon="phone-arrow-up-right" href="/campaigns/dialing-and-retries">
    Understand how attempts, retries, and concurrency drive the stats buckets.
  </Card>
</CardGroup>
