> This page is for Cloud Call Center.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.hams.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.hams.ai/_mcp/server.

# Call history and recordings

> Read the sealed record of every call, download the audio of a recorded call, and read its transcript.

The API has 2 read scopes. The `cdrs:read` scope opens `GET /cdrs`, the call history of the
account. The `recordings:read` scope opens `GET /recordings`, the audio download, and the transcript.

## List calls

```bash
curl "https://telephony.hams.ai/api/developer/v1/cdrs?direction=inbound&outcome=answered&limit=50" \
  -H "Authorization: Bearer $CCC_KEY"
```

The list is cursor-paginated and shows the newest call first. If you send no `from`, the list has
only the calls of the last 7 days. A range of more than 92 days returns `422`. A `from` value after `to` also returns `422`. A `from` or `to`
value without a timezone also returns `422`:

```json
{
  "data": [ { "id": "0192...", "direction": "inbound", "outcome": "answered", "...": "..." } ],
  "has_more": true,
  "next_cursor": "eyJz...",
  "total_count": null
}
```

Send `next_cursor` as `cursor` to get the next page. `limit` is 1 to 100. The default is 25. Set
`include_total=true` to fill `total_count`. This causes an extra count query.

| Filter                         | Matches                                                                                                                                        |
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `direction`                    | `inbound`, `outbound`, or `internal`                                                                                                           |
| `outcome`                      | `answered`, `no_answer`, `busy`, `abandoned`, `canceled`, `failed`, or `voicemail`                                                             |
| `agent`                        | The extension number of the agent that handled the call                                                                                        |
| `from`, `to`                   | Calls that started in the range (ISO 8601, with a timezone). The range is 92 days or less. Without `from`, the range is the 7 days before `to` |
| `number`                       | A substring of the caller number or the callee number. It has 3 characters or more                                                             |
| `min_duration`, `max_duration` | The total seconds of the call                                                                                                                  |

## What a record contains

Each row is one call. The system seals the row a few seconds after the call ends. The `id` is the
one id of the call. It is the same `id` that the live board and every webhook use.

| Group      | Fields                                                                                                                                                                                                                                         |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Identity   | `id`, `direction`                                                                                                                                                                                                                              |
| Parties    | `from_number`, `from_type`, `to_number`, `to_type`. A `type` is `external`, `did`, or `extension`                                                                                                                                              |
| Outcome    | `outcome`, `outcome_detail`                                                                                                                                                                                                                    |
| Timing     | `started_at`, `answered_at`, `ended_at`, `total_seconds`, `talk_seconds`, `ring_seconds`, `queue_wait_seconds`                                                                                                                                 |
| Queue      | `queue_handled`, `queues_touched`                                                                                                                                                                                                              |
| Agent      | `agent_user_id`, `agent_extension_snapshot`, `agent_name_snapshot`                                                                                                                                                                             |
| Recording  | `recording_id`, `recording_status`                                                                                                                                                                                                             |
| Transcript | `transcript_status`, `transcript_error`                                                                                                                                                                                                        |
| Analysis   | `analysis_status`, `analysis_outcome`, `analysis_sentiment_end`, `analysis_agent_score`, `analysis_labels`                                                                                                                                     |
| Account    | `account_id`, `domain_id`, `did_id`, `trunk_id`                                                                                                                                                                                                |
| Quality    | `data_quality`. `complete` means the record is final. `partial` means the carrier record of the call has not arrived yet, and the timings can still change. `conflicted` means the engine recorded an answer but the queue recorded an abandon |

The record has no `sip_call_id`. The SIP Call-ID is not part of the list. Use `id` to identify a call.

### Analysis fields

The analysis fields come from post-call analysis. They are `null` when the call has no analysis. See
[Post-call analysis](/cloud-call-center/post-call-analysis).

| Field                    | Meaning                                                                                                                 |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| `analysis_status`        | `pending`, `completed`, `skipped`, or `failed`                                                                          |
| `analysis_outcome`       | `resolved`, `unresolved`, `escalated`, or `customer_silent`. `null` until the analysis is complete                      |
| `analysis_sentiment_end` | The mood of the customer at the end of the call: `very_negative`, `negative`, `neutral`, `positive`, or `very_positive` |
| `analysis_agent_score`   | 1 to 5 for the human agent. `null` when no human agent spoke                                                            |
| `analysis_labels`        | The labels from the profile that apply to the call                                                                      |

The list has no analysis filters. For the summary and the other fields of a result, use the
`call.analysis_completed` webhook.

### Numbers on an outbound call

On an outbound record, `from_number` is the caller ID that the carrier saw. `from_type` is `did`.
`to_number` is the number that the system dialed, after the `callee_out` rules of the route. The
extension that placed the call is in `agent_extension_snapshot`.

### Outcome and outcome detail

`outcome` has a closed list of values. Branch on this field. The values are `answered`,
`no_answer`, `busy`, `abandoned`, `canceled`, `failed`, and `voicemail`.

`outcome_detail` gives the reason. The values are:

| Value                                                    | Meaning                                                                                                                                                                 |
| -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `direct`                                                 | A dial node leg decided the call, answered or not                                                                                                                       |
| `queue`                                                  | A queue agent handled the call                                                                                                                                          |
| `ring_group`                                             | A ring group decided the call                                                                                                                                           |
| `transfer`                                               | The call was answered after a transfer                                                                                                                                  |
| `queue_timeout`                                          | The caller waited in a queue until the queue timed out, or the call overflowed out of the queue                                                                         |
| `no_agents`                                              | The queue had no agent available                                                                                                                                        |
| `in_queue`                                               | The caller was in a queue when the call ended                                                                                                                           |
| `in_flow`                                                | The caller was in a call flow when the call ended                                                                                                                       |
| `blocked`                                                | The queue refused the caller                                                                                                                                            |
| `external`                                               | A trunk leg to an external system decided the call                                                                                                                      |
| `outbound`                                               | An outbound leg decided the call. This includes a call that the API placed, answered or not, and a call that an extension dialed from its phone and the callee answered |
| `voice_agent`                                            | A voice agent handled the call. The `outcome` is `answered`                                                                                                             |
| `unreachable`                                            | The system could not reach a channel to the destination                                                                                                                 |
| `congestion`                                             | The carrier or the network was congested                                                                                                                                |
| `agent_declined`, `agent_no_answer`, `agent_unreachable` | The agent side of a call that the API placed did not connect. See [Outbound calls](/cloud-call-center/api/outbound-calls)                                               |

A blind transfer counts as answered only when the target answers. The system adds new details
without notice. Treat a value that you do not know as information only.

## Recordings

```bash
curl "https://telephony.hams.ai/api/developer/v1/recordings?page=1&items_per_page=50" \
  -H "Authorization: Bearer $CCC_KEY"
```

A recording row has these fields:

| Field                                             | Meaning                                                                                                      |
| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `id`                                              | The id of the recording                                                                                      |
| `call_id`                                         | The `id` of the call. It can be `null`                                                                       |
| `status`                                          | `pending`, `ready`, or `failed`                                                                              |
| `error`                                           | The reason for a failure: `missing_capture`, `empty_capture`, `timeout`, or `merge_failed`. Otherwise `null` |
| `account_id`                                      | The id of the account                                                                                        |
| `started_at`, `ended_at`                          | The time range of the recording                                                                              |
| `duration_ms`, `size_bytes`, `format`, `channels` | The properties of the audio                                                                                  |
| `transcript_status`                               | `pending`, `ready`, `failed`, or `skipped`. `null` if no transcript exists                                   |
| `transcript_error`                                | `too_long` or `unavailable`. Otherwise `null`                                                                |
| `created_at`, `updated_at`                        | The times when the system created and last changed the row                                                   |

`page` starts at 1. `items_per_page` is 1 to 100. The default is 10. To match a recording to a call,
use the `call_id` of the recording or the `recording_id` of the call.

`GET /recordings/{id}` returns one row. `GET /recordings/{id}/audio` answers `307` with a
short-lived signed URL for the merged stereo WAV. The customer is on the left channel. The agent is on
the right channel. Add `?blended=true` to get a rendition with less channel separation. If that rendition does not exist, the API returns `404`.

```bash
curl -L "https://telephony.hams.ai/api/developer/v1/recordings/0192.../audio" \
  -H "Authorization: Bearer $CCC_KEY" -o call.wav
```

> **Warning**
>
> Follow the `307` redirect. The audio endpoint does not send audio bytes itself, so a client that does
> not follow redirects gets no audio. Do not store the signed URL, because it expires.

| Status | Meaning                                                                                                  |
| ------ | -------------------------------------------------------------------------------------------------------- |
| `409`  | The system has not finished the merge of the recording. Wait for `call.recording_ready` or poll `status` |
| `404`  | The recording does not exist, or the merge failed and the audio does not exist                           |

Only calls that a trunk, DID, outbound route, or queue marks for recording have a recording. See
[Call history and recordings](/cloud-call-center/call-history) for the recording policies.

### Public audio link

The `call.recording_ready` webhook carries `recording_url`. This link points to
`GET /recordings/public/{token}/audio`. The route needs no API key. It answers `307` with a signed
URL for the merged stereo WAV. Your client must follow the redirect. The link expires at `recording_url_expires_at`, 24 hours after the webhook by default. After
that time it returns `404`. A forged token also returns `404`. After the link expires, use
`GET /recordings/{id}/audio` with your key.

## Transcripts

A transcript is the text of a recording. The system merges the customer channel and the agent
channel into one dialogue in time order. A transcript exists only when call transcription is on for
your account. When a recording finishes its merge and transcription is on, the system makes the
transcript automatically. An administrator can also request a transcript in the dashboard. The
developer API only reads transcripts.

`transcript_status` on a recording row and on a call record shows the state:

| Value     | Meaning                                                        |
| --------- | -------------------------------------------------------------- |
| `pending` | The transcript is in progress                                  |
| `ready`   | The transcript is available                                    |
| `failed`  | The transcript failed. `transcript_error` gives the reason     |
| `skipped` | The system skipped the recording because transcription was off |

`transcript_error` is `too_long` when the recording is too long to transcribe. It is `unavailable`
for any other failure.

```bash
curl "https://telephony.hams.ai/api/developer/v1/recordings/0192.../transcript" \
  -H "Authorization: Bearer $CCC_KEY"
```

`GET /recordings/{id}/transcript` needs the `recordings:read` scope. The response contains
`id`, `recording_id`, `status`, `provider`, `dialogue`, `created_at`, and `updated_at`:

```json
{
  "id": "0192...",
  "recording_id": "0192...",
  "status": "ready",
  "provider": null,
  "dialogue": [
    { "speaker": "agent", "text": "Hello, how can I help?", "start_sec": 0.4, "end_sec": 2.1 },
    { "speaker": "customer", "text": "I need help with my order.", "start_sec": 2.6, "end_sec": 4.8 }
  ],
  "created_at": "2026-09-22T10:03:00+00:00",
  "updated_at": "2026-09-22T10:04:10+00:00"
}
```

Each turn has a `speaker` (`customer` or `agent`), the `text`, and the `start_sec` and `end_sec` of
the speech in the recording.

| Status | Meaning                                                                                        |
| ------ | ---------------------------------------------------------------------------------------------- |
| `409`  | The transcript is in progress. Try again later                                                 |
| `404`  | No transcript exists. The transcript failed, or the system skipped it, or transcription is off |

## Reconcile from webhooks

`call.completed` carries the same core fields as a call record. `call.recording_ready` gives the
`recording_id` and the `call_id` when the audio is ready. `call.analysis_completed` gives the full
result of a post-call analysis. If you store these events, you need
the endpoints on this page only to fill gaps or to answer a single question. See
[Webhooks](/cloud-call-center/api/webhooks).

## Related

* [Webhooks](/cloud-call-center/api/webhooks)
* [Outbound calls](/cloud-call-center/api/outbound-calls)
* [Errors](/cloud-call-center/api/errors)
* [Call history and recordings](/cloud-call-center/call-history)
* [API Reference](/cloud-call-center/reference)