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

# Conversations and recordings

> List and filter calls, then read a call's transcript, recording, tool calls and metrics.

Every call an agent takes or places is a **conversation**. Use these endpoints to sync calls into your own systems, build reports, or pull the audio and transcript of one call.

| What you want | Endpoint |
| - | - |
| A page of conversations, filtered | `GET /v1/projects/{projectId}/conversations` |
| One conversation, with its summary | `GET /v1/projects/{projectId}/conversations/{id}` |
| The call legs of a conversation | `GET /v1/projects/{projectId}/conversations/{id}/calls` |
| Transcript, recording link, tool calls | `GET /v1/projects/{projectId}/conversations/{id}/artifacts` |
| Tokens, latency, turns | `GET /v1/projects/{projectId}/conversations/{id}/metrics` |

## List conversations

```bash theme={null}
curl -G https://voice-api.onclarity.com/v1/projects/$PROJECT_ID/conversations \
  -H "Authorization: Bearer $CLARITY_API_KEY" \
  --data-urlencode "agent=$AGENT_ID" \
  --data-urlencode "date_from=2026-10-01T00:00:00Z" \
  --data-urlencode "date_to=2026-10-31T23:59:59Z" \
  --data-urlencode "is_eval=false" \
  --data-urlencode "page_size=50" \
  --data-urlencode "offset=0"
```

```json theme={null}
{
  "conversations": [
    {
      "id": "0b6f6c1e-5d0a-4f43-9a51-3e2f3b7f1a10",
      "project_id": "3f6c1a52-8e0b-4f6e-9a1d-2b7c4e9f0a11",
      "status": "ended",
      "direction": "incoming",
      "connected": true,
      "duration": 94,
      "hangup_reason": "customer_hangup",
      "initial_agent_id": "5e1d7c2a-0f4b-4b8e-8c3d-9a6f2e1b7c40",
      "agent_version_id": "a7c2e9d1-4b3f-4e6a-8d2c-1f0b9e8a7c65",
      "origin": "production",
      "conversation_summary": "The caller asked to move an appointment to Thursday.",
      "summary_status": "generated",
      "call_language": null,
      "csat_score": null,
      "campaign_id": null,
      "campaign_item_id": null,
      "warnings": [],
      "participants": [
        {
          "type": "call",
          "call_id": "e3b1c9a0-7d24-4f8b-a6e5-2c9d0f1b8a37",
          "call_type": "pstn",
          "direction": "incoming",
          "phone_number": "+14155550123",
          "did": "+14155550100",
          "remote_number": "+14155550123",
          "joined_at": "2026-10-20T14:02:00.000Z",
          "left_at": "2026-10-20T14:03:34.000Z",
          "hangup_reason": "customer_hangup",
          "duration": 94,
          "is_eval": false,
          "eval_submission_id": null
        }
      ],
      "created_at": "2026-10-20T14:02:00.000Z",
      "updated_at": "2026-10-20T14:03:40.000Z",
      "ended_at": "2026-10-20T14:03:34.000Z"
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0
}
```

The conversation above is trimmed: participants of `type: "agent"` and a few other fields are left out.

### Filters

All filters are optional query parameters and combine with AND.

| Parameter | Values | Matches |
| - | - | - |
| `agent` | Agent id | Conversations the agent took part in. |
| `direction` | `incoming`, `outgoing` | |
| `status` | `pending`, `active`, `ended` | |
| `connected` | `true`, `false` | Whether the caller (or callee) joined. A conversation that never connected has no transcript. |
| `date_from`, `date_to` | ISO 8601 | `created_at` is within the range, both ends included. |
| `phone` | Any part of a number | Your number or the other party's number contains it. |
| `caller` | Any part of a number | The other party's number contains it. |
| `callee` | Any part of a number | Your number contains it. |
| `call_type` | `pstn`, `sip`, `webrtc`, `whatsapp` | A call leg of that type. |
| `is_eval` | `true`, `false` | `false` hides test calls placed by [evals](/voice/evals.md); `true` shows only them. Omit it to get both. |
| `has_warnings` | `true`, `false` | Conversations with (or without) an in-call warning. |
| `warning_category` | Category name; repeat the parameter or separate with commas | A warning of any of these categories. |
| `warning_severity` | `warning`, `critical` | `critical` matches critical warnings only. |
| `has_errors` | `true` | Conversations with an error found in the call's timeline. |
| `has_findings` | `true` | An error or a warning. |
| `include_abandoned` | `true` | Also return sessions that were opened but never turned into a call. Hidden by default. |

### Sorting and pagination

| Parameter | Default | Notes |
| - | - | - |
| `sort_by` | `created_at` | `created_at`, `duration` or `ended_at`. |
| `sort_dir` | `desc` | `asc` or `desc`. |
| `page_size` | 20 | 1 to 100. Values outside the range are clamped. |
| `offset` | 0 | Rows to skip. |

`total` is the number of conversations that match the filters, so you can page with `offset` until `offset + limit >= total`. When you sync continuously, sort by `created_at` ascending with a `date_from` you advance, rather than paging through a list that grows while you read it.

> [!TIP]
>
> An unknown value is not an error. An unrecognized `status` simply matches nothing, and an unrecognized `sort_by` falls back to `created_at`.

### Conversation fields

| Field | Meaning |
| - | - |
| `status` | `pending` while the call is set up, `active` while it runs, `ended` after. |
| `origin` | `production` for real traffic, `try_it` and `demo` for browser tests, `eval` for [eval](/voice/evals.md) calls. |
| `connected` | Whether the other party joined. |
| `duration` | Seconds. |
| `initial_agent_id`, `agent_version_id` | The agent that took the call and the exact version it ran. |
| `conversation_summary`, `summary_status` | The agent's summary of the call. `summary_status` is `not_enabled`, `pending`, `generated`, `skipped` or `failed`. The summary is written shortly after the call ends. |
| `call_language` | For agents that speak two languages, the language the call was mostly held in. `null` otherwise. |
| `csat_score` | 1 to 5 when the caller rated the call. |
| `campaign_id`, `campaign_item_id` | Set when a [campaign](/voice/campaigns.md) placed the call. |
| `warnings` | In-call warnings raised by the agent's monitoring, most severe first. |
| `pii_redaction_status` | `not_required`, `pending`, `in_progress` or `applied`. While it is `pending` or `in_progress`, the transcript and recording are held back. |
| `participants` | The call legs (`type: "call"`) and agent sessions (`type: "agent"`) of the conversation. |

To list call legs across the whole project instead, use `GET /v1/projects/{projectId}/calls`. It accepts most of the same filters and returns `{ "calls": [...] }`.

## Read the transcript and recording

The artifacts endpoint returns everything recorded for one conversation, one entry per agent session:

```bash theme={null}
curl -G https://voice-api.onclarity.com/v1/projects/$PROJECT_ID/conversations/$CONVERSATION_ID/artifacts \
  -H "Authorization: Bearer $CLARITY_API_KEY" \
  --data-urlencode "include_recording=true"
```

```json theme={null}
{
  "project_id": "3f6c1a52-8e0b-4f6e-9a1d-2b7c4e9f0a11",
  "conversation_id": "0b6f6c1e-5d0a-4f43-9a51-3e2f3b7f1a10",
  "sessions_count": 1,
  "sessions_degraded": 0,
  "sessions": [
    {
      "session_id": "AJ_k3P9mQ2",
      "call_id": null,
      "agent_id": "5e1d7c2a-0f4b-4b8e-8c3d-9a6f2e1b7c40",
      "joined_at": "2026-10-20T14:02:00.000Z",
      "left_at": "2026-10-20T14:03:34.000Z",
      "transcript": "[00:00.00] (L3) AGENT: Hi, this is Clarity Dental. How can I help?\n[00:03.10] (L9) USER: I need to move my appointment.\n[00:08.40] (L14) TOOL find_slots({\"date\":\"2026-10-22\"}) → ok, 812ms, 3 slots",
      "recording_signed_url": "https://storage.example.com/calls/AJ_k3P9mQ2/recording.wav?signature=abc123",
      "recording_signed_url_expires_at": "2026-10-20T15:10:00.000Z",
      "fatal_count": 0,
      "warning_count": 0
    }
  ],
  "merged": {
    "transcript": "[00:00.00] (L3) AGENT: Hi, this is Clarity Dental. How can I help?\n[00:03.10] (L9) USER: I need to move my appointment.",
    "fatal_count": 0,
    "warning_count": 0
  },
  "messages": [],
  "handoffs": []
}
```

The example is trimmed. Each session also carries `metadata`, the raw `timeline_jsonl`, `findings`, `turn_timings` and per-turn model metrics.

| Query parameter | Effect |
| - | - |
| `include_recording=true` | Adds `recording_signed_url` and `recording_signed_url_expires_at` to each session. Without it both are `null`. |
| `include_session_config=true` | Adds `session_config`, the full configuration the agent ran with. |

### Transcript format

`transcript` (per session) and `merged.transcript` (all sessions in order) are plain text, one line per event:

```text theme={null}
[MM:SS.ss] (Lnnn) ROLE: text
[MM:SS.ss] (Lnnn) TOOL name(arguments) → ok, 812ms, result
```

* The time is relative to the start of the session.
* `ROLE` is `AGENT` for the AI, `USER` for the caller, `OPERATOR` for a person handling a Voice Assist call, `TOOL` for a tool call with its outcome, duration and result, and `END` for how the call ended. A few other roles mark the agent's internal prompts.
* `(Lnnn)` points to the line of `timeline_jsonl` the entry came from. Long tool arguments and results are shortened.

If you want structured lines rather than text, use the `dialog` entries of the [call-ended vCon](/voice/call-ended-webhook.md#dialog), or parse `timeline_jsonl`.

### Download the recording

`recording_signed_url` is a pre-signed link to the session's WAV file. Fetch it without the `Authorization` header:

```bash theme={null}
curl -o call.wav "$RECORDING_SIGNED_URL"
```

* The link is valid for **one hour**. Request the artifacts again for a new one.
* It is `null` when the session has no recording.
* A conversation with several agent sessions, for example after a hand-off between agents, has one recording per session.

### When artifacts are not ready

| Status | `code` | Meaning |
| - | - | - |
| `404` | `ARTIFACTS_NOT_READY` | The recording and transcript are still being uploaded, or personal data is still being redacted. Retry in a minute. A call that was aborted before anything was stored also answers this way. |
| `404` | `CONVERSATION_NOT_FOUND` | No such conversation in this project. |
| `502` | `OBJECT_STORAGE_ERROR` | Storage could not be read. Retry later. |

When only some sessions fail, the request succeeds: the failed sessions carry an `error` object and `sessions_degraded` counts them.

A conversation with no agent session at all, for example a call that never reached an agent, answers `200` with an empty `sessions` list.

## Metrics

```bash theme={null}
curl https://voice-api.onclarity.com/v1/projects/$PROJECT_ID/conversations/$CONVERSATION_ID/metrics \
  -H "Authorization: Bearer $CLARITY_API_KEY"
```

```json theme={null}
{
  "total_tokens": 18342,
  "prompt_tokens": 17120,
  "completion_tokens": 1222,
  "cached_tokens": 9984,
  "tool_call_count": 2,
  "e2e_median_latency_ms": 1180,
  "num_turns": 9,
  "resolution_state": "resolved",
  "resolution_reason": "The appointment was moved and the caller confirmed the new time."
}
```

* `e2e_median_latency_ms` is the agent's response latency in milliseconds: the median over the conversation's call legs, leaving out legs whose latency is above 10 seconds.
* `resolution_state` and `resolution_reason` come from Clarity's resolution judge, when it has judged the call.
* Every field is `null` when the call has no metrics yet, or none were recorded.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.