A recorded call produces a single audio object in your tenant’s storage. The API over it is deliberately small: Everything is keyed by call_sid — the same id the CDR, the transcript, the usage rows, and (for an external agent) ctx.job.id all use. That is the join key across every surface.

Authenticate

Use an org-scoped mpk_… key, minted in the console under Settings → API keys. Scopes are per-operation and narrow: a key minted to fetch recordings cannot place a call.

List recordings

number
default:"200"
1–500 rows per page.
number
default:"0"
Pagination offset.
number
Epoch milliseconds, inclusive, on call start.
"inbound" | "outbound"
string
Case-insensitive substring on the CDR’s agent attribution.
Case-insensitive substring over call_id, caller, and callee.
Each row: stats describes exactly the filtered set the rows come from — total_recordings, total_duration, total_bytes, avg_duration — so a dashboard’s KPI cards and its grid can never disagree.
Two eras, merged for you. Recordings written before 2026-06-03 carry a cdrs.recording_url; everything since is a storage object discovered by listing your tenant prefix. The operation merges both, one row per call, newest-first. You do not need to know which era a call belongs to.

Download one recording

recording_url is an internal s3:// locator. To read the audio, mint a presigned GET — valid for one hour:
The call must belong to your organization: the server resolves the object only within your tenant, so guessing another org’s call_sid returns NOT_FOUND rather than a URL. Objects in the shared fallback bucket are additionally proven against your CDR rows before a URL is minted. Files are WAV (or Opus) named <call_sid>.<ext>, both legs mixed.
Mint the URL when you are about to fetch, not when you list. It expires in an hour, and each mint is recorded as a playback/download event against the requesting user — presigning en masse produces misleading audit data.

Get pushed instead of polling

Subscribe to voice.recording.ready and the platform tells you the moment the upload completes — no list-and-diff loop. Add the Transcripts & recordings event group to a webhook endpoint in the console (it carries both voice.transcript.ready and voice.recording.ready).
Then presign call_id and fetch. Webhook deliveries are signed — verify X-Clutchcall-Signature (t=…,v1=…, HMAC over "<t>.<body>") before you trust one. See Webhooks.

Storage and retention

storage.info reports what your tenant is holding and the retention window:
Recordings are retained 30 days by default.

Recording an external agent’s calls

Recording is a call-layer capture (the RTP tap), not an agent-pipeline feature. A call handled by your own livekit-agents worker over our transport is therefore recorded exactly like a native agent’s call — both legs, same storage, same API. Nothing in your agent needs to participate. Correlate it from inside your agent by logging ctx.job.id, which is the same call_sid this API is keyed by.

Webhooks

Every event type, the signature scheme, and retry behaviour.

Admin API SDK

The typed client, the key model, and the full operation surface.

Observability

Where recordings sit beside CDR, MOS, and transcripts.

LiveKit Agent over TeleQuick Transport

What an external agent does and does not get automatically.