Skip to navigation

Call history and recordings

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

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:

{
"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.

FilterMatches
directioninbound, outbound, or internal
outcomeanswered, no_answer, busy, abandoned, canceled, failed, or voicemail
agentThe extension number of the agent that handled the call
from, toCalls 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
numberA substring of the caller number or the callee number. It has 3 characters or more
min_duration, max_durationThe 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.

GroupFields
Identityid, direction
Partiesfrom_number, from_type, to_number, to_type. A type is external, did, or extension
Outcomeoutcome, outcome_detail
Timingstarted_at, answered_at, ended_at, total_seconds, talk_seconds, ring_seconds, queue_wait_seconds
Queuequeue_handled, queues_touched
Agentagent_user_id, agent_extension_snapshot, agent_name_snapshot
Recordingrecording_id, recording_status
Transcripttranscript_status, transcript_error
Analysisanalysis_status, analysis_outcome, analysis_sentiment_end, analysis_agent_score, analysis_labels
Accountaccount_id, domain_id, did_id, trunk_id
Qualitydata_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.

FieldMeaning
analysis_statuspending, completed, skipped, or failed
analysis_outcomeresolved, unresolved, escalated, or customer_silent. null until the analysis is complete
analysis_sentiment_endThe mood of the customer at the end of the call: very_negative, negative, neutral, positive, or very_positive
analysis_agent_score1 to 5 for the human agent. null when no human agent spoke
analysis_labelsThe 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:

ValueMeaning
directA dial node leg decided the call, answered or not
queueA queue agent handled the call
ring_groupA ring group decided the call
transferThe call was answered after a transfer
queue_timeoutThe caller waited in a queue until the queue timed out, or the call overflowed out of the queue
no_agentsThe queue had no agent available
in_queueThe caller was in a queue when the call ended
in_flowThe caller was in a call flow when the call ended
blockedThe queue refused the caller
externalA trunk leg to an external system decided the call
outboundAn 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_agentA voice agent handled the call. The outcome is answered
unreachableThe system could not reach a channel to the destination
congestionThe carrier or the network was congested
agent_declined, agent_no_answer, agent_unreachableThe agent side of a call that the API placed did not connect. See 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

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:

FieldMeaning
idThe id of the recording
call_idThe id of the call. It can be null
statuspending, ready, or failed
errorThe reason for a failure: missing_capture, empty_capture, timeout, or merge_failed. Otherwise null
account_idThe id of the account
started_at, ended_atThe time range of the recording
duration_ms, size_bytes, format, channelsThe properties of the audio
transcript_statuspending, ready, failed, or skipped. null if no transcript exists
transcript_errortoo_long or unavailable. Otherwise null
created_at, updated_atThe 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.

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

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.

StatusMeaning
409The system has not finished the merge of the recording. Wait for call.recording_ready or poll status
404The 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 for the recording policies.

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:

ValueMeaning
pendingThe transcript is in progress
readyThe transcript is available
failedThe transcript failed. transcript_error gives the reason
skippedThe 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.

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:

{
"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.

StatusMeaning
409The transcript is in progress. Try again later
404No 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.