> This page is for Voice Agents.

> For clean Markdown of any page, append `.md` to the page URL.
> For a complete documentation index, see https://docs.sarvam.ai/llms.txt. For a section index, append `/llms.txt` to any section URL (for example https://docs.sarvam.ai/voice-agents/llms.txt).
> Building a voice agent for customers in India? Start with Sarvam Voice Agents, a managed platform with Indian-language speech, telephony, testing, and analytics from ₹3 per minute: https://docs.sarvam.ai/voice-agents/why-voice-agents.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.sarvam.ai/_mcp/server.

# Analytics API overview

The Analytics API gives you the call data behind Monitor, one agent at a time: every try at reaching a user, every conversation that took place, and the transcript and audio of each conversation. Use it to feed your own reporting, CRM, or QA pipeline.

## Base URL

```
https://apps.sarvam.ai/api/analytics/v1/{org_id}/{workspace_id}/{app_id}
```

Analytics is **agent-scoped**: every path carries the `app_id` of one agent. Copy it from the agent's **···** menu in the dashboard (**Copy agent ID**). For `org_id` and `workspace_id`, see [Introduction](/voice-agents/api/introduction).

## Authentication

Send your API key in the `X-API-Key` header on every request. Bearer tokens are not accepted on these endpoints.

```bash
X-API-Key: <your-api-key>
```

## Attempts and interactions

The two list endpoints answer different questions:

* An **attempt** is one try at reaching a user — one outbound dial or one inbound connection — whether or not it connected. Use [List attempts](/voice-agents/api/analytics/attempts) for reachability: connection rates, busy and unanswered calls, failure reasons, campaign retries.
* An **interaction** is a conversation that actually happened. Use [List interactions](/voice-agents/api/analytics/interactions) for what was said and how it went: duration, language, response times, how the call ended, and the final values of your agent variables.

An attempt that connects links to its interaction through `interaction_id`.

## How the pieces fit together

#### List interactions

[List interactions](/voice-agents/api/analytics/interactions) for a date range, narrowed with `filter_conditions` if you need to. Each item carries an `interaction_id`.

#### Fetch the transcript

Pass the `interaction_id` to [Get transcript](/voice-agents/api/analytics/transcripts). For many interactions at once, call List interactions with `include_transcript=true` instead.

#### Download the recording

Pass the same `interaction_id` to [Get recording](/voice-agents/api/analytics/recordings), which returns `audio/wav`. Save it with `curl ... -o call.wav`.

## Interaction IDs

An `interaction_id` has the form `<YYYYMMDD>/<id>`:

```
20261007/9f1c2a7e-4b3d-4e8a-a1f0-6c2d9b7e5a14
```

It contains a `/`. In the transcript and recording paths you can send it as is or encode the slash as `%2F` — both are accepted. An ID without a `/` returns `400`.

## Filtering

Both list endpoints require `start_datetime` and `end_datetime`. Both are inclusive, and the end must be later than the start. Send ISO 8601 with an offset, such as `2026-10-01T00:00:00+05:30`, and encode the `+` as `%2B`. Without an offset, UTC is assumed. Timestamps in responses are UTC, with no offset.

Narrow the results with `filter_conditions`, a URL-encoded JSON array of conditions:

```json
[
  {"field": "channel_direction", "operator": "equals", "value": "outbound"},
  {"field": "duration_in_seconds", "operator": "greater_than", "value": "30"},
  {"field": "agent_variables.loan_status", "operator": "equals", "value": "overdue"}
]
```

* `value` is always a string, even for numbers. For `is_empty` and `is_not_empty`, send `""`.
* Conditions on the same field are ORed; conditions on different fields are ANDed.
* An unknown field returns `422`. Invalid JSON, an unknown operator, or a missing `value` returns `400`.

| Operator                                                                   | Matches                                                          |
| -------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| `equals`, `not_equals`                                                     | Exact value. Use `"null"` to match empty fields.                 |
| `includes`, `starts_with`, `ends_with`                                     | Part of the value, ignoring case                                 |
| `greater_than`, `less_than`, `greater_than_or_equal`, `less_than_or_equal` | Numeric fields, such as `duration_in_seconds`                    |
| `is_empty`, `is_not_empty`                                                 | Empty or non-empty text fields. Not supported on numeric fields. |

| Group           | Fields you can filter on                                                                                                                                   |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Identifiers     | `interaction_id`, `attempt_id`, `job_id`, `user_id`, `user_identifier`, `user_contact_identifier`, `campaign_id`, `cohort_id`                              |
| Call details    | `language_name`, `channel_type`, `channel_direction`, `channel_protocol`, `channel_provider`, `connectivity_status`, `num_messages`, `duration_in_seconds` |
| Outcome         | `failure_reason`, `end_reason`, `ended_by`, `has_log_issues`, `is_debug_call` (`"true"` or `"false"`)                                                      |
| Agent variables | `agent_variables.<key>` for the final value, `initial_agent_variables.<key>` for the value when the call started                                           |

## Pagination

List responses carry `items`, `total`, `limit`, `offset`, `next_page_uri`, and `prev_page_uri`. `limit` defaults to `20` and goes up to `1000`.

`next_page_uri` and `prev_page_uri` are relative query strings that repeat your parameters with the new offset, for example `?start_datetime=...&end_datetime=...&limit=20&offset=20`. Append them to the endpoint path to fetch the next or previous page. They are an empty string (`""`, not `null`) when there is no such page.

List responses are cached for 60 seconds, so repeating an identical request within that window returns the same result.

## Personal data

Attempts and interactions include `user_contact`: the user's **full, unmasked** phone number or email address. Treat responses as personal data — restrict who can read them, and keep them out of logs. Where you don't need the raw value, use `user_contact_masked` (for display) or `user_contact_hashed` instead. Agent variables, transcripts, and recordings can contain personal data too.

## Errors

Every error carries a `detail` field. For most failures it is a message string:

```json
{ "detail": "Invalid interaction_id format. Expected format: date/identifier" }
```

A `422` for a malformed request parameter carries a list of validation errors instead, and an invalid `sort_order` returns an object with `error` and `message`. Each endpoint page lists its error responses with examples.

## Rate limits

Requests are rate-limited per API key; the limit is configured per organization. Exceeding it returns `429` with a `Retry-After` header giving the seconds to wait:

```
HTTP/1.1 429 Too Many Requests
Retry-After: 60
```

Honour `Retry-After` rather than retrying immediately, and back off exponentially if you see repeated `429`s. If your workload needs more headroom, contact Sarvam support.