Recording captures both legs of a call as a single stereo file, keyed on the call’s sid. The caller is on the left channel. The agent (AI or human) is on the right channel. Because the two speakers land on separate channels, you can run each side through its own transcript or scorer without a speaker-ID model. After a call ends, the pipeline uploads the recording to your workspace’s object storage. It stamps the URL onto the call’s CDR. The control plane can then give you a time-limited download link.
Recording is a workspace-level setting today. There is no per-call record: true flag on calls.originate(...), and CallData carries no recording field. Turn recording on once for the workspace, in the voice console or via your operator. The system then records every call on that workspace. You cannot opt a single call in or out from the SDK. A per-call record toggle is not shipped yet.

How it works

1

Capture (automatic once enabled)

The media plane taps both RTP legs. It mixes them into a stereo stream (caller = L, agent = R). It streams the result to the recording pipeline. No SDK call is involved.
2

Finalize

On hang-up, the pipeline writes a stereo WAV to your workspace’s object storage. It stamps recording_url and the byte size onto the call’s CDR.
3

Fetch

Read the CDR to find the recording. Then mint a short-lived presigned download URL keyed on the sid.

Find recordings and get a download URL

The recording URL is stored on the CDR, not on the live Call object. For this reason, you fetch it through the control-plane API, not through the audio SDK. Two routes do the job. cdr.recordings lists calls that produced a recording. storage.signedUrl returns a one-hour presigned download link for a given sid. Both routes authenticate with your API key.
The presigned URL expires in one hour. Mint it when you are about to download, not when you list. Requests are tenant-scoped. You can only presign recordings that belong to your orgId, even if you guess another workspace’s sid.

React to a finished recording (native SDK cores)

The native SDK cores (Python, Go, Rust, and the other FFI wrappers) send a RECORDING_READY call event when the file is finalized. The event carries recording_url and duration_seconds. With it, you can process recordings as they complete instead of polling the CDR.
The native cores expose this event stream. The browser/TypeScript voice SDK has no recording-event surface. From a browser or Node app, use the CDR + storage.signedUrl path above.

Analyze: split the channels

The download is a stereo WAV. The caller is on the left channel and the agent is on the right channel. Split the two channels to get a clean per-speaker signal. This channel split is your speaker separation. The recording path has no built-in diarization or voiceprint. Run each channel through your own ASR, or any scorer, independently.
Speaker separation here comes entirely from the stereo channel split. The voice path has no speaker-ID, diarization, or voiceprint model. If both speakers were mixed to mono, you could not tell them apart. The two-channel recording is what makes per-speaker analysis reliable.

Pair it with call quality

Every recorded call also has objective quality data on its CDR: an ITU-T E-model MOS estimate, plus jitter and packet loss. The media plane computes these values. Join them to the recording when you score a call. Then you can attribute a low transcript confidence to a bad line rather than a bad agent. See MOS, jitter & loss for the fields and how the system derives them.

MOS, jitter & loss

The per-call quality metrics that live alongside the recording URL.

Call traces

Follow a single sid across signalling, media, and agent turns.

Stream audio to ASR

Transcribe live instead of after the fact. Feed uplink into your recognizer.

Observability overview

Recordings, CDRs, dashboards, and how they fit together.