recording_url, and objects listed from
the recording bucket — then applies one shared filter predicate to the
merged set. Understanding that merge explains most of the screen’s
surprising behaviour.
Where the list comes from: CDRs plus bucket objects
cdr.recordings runs two lookups in parallel for the active org:
Because a single call can produce several CDR rows, the CDR side groups
by
call_id first. Within a group the screen shows:
max(starting_time)as the timestamp,max(duration)andmax(recording_size_bytes),- the latest-by-
starting_timevalue forcaller,callee,direction,agentandrecording_url.
recording_url becomes
s3://<bucket>/<object-key> and its sizeBytes becomes the displayed
file size. The call metadata (participants, direction, duration, agent)
still comes from the CDR lookup.
For every bucket object, the procedure also queries cdrs for that
tenant restricted to the listed call ids, to recover participant metadata
that the object itself does not carry.
Rows without CDR attribution, and shared-bucket safety
An object in the bucket knows its call id, its size and when it was last modified. It does not know who was on the call. That metadata only exists incdrs. So two outcomes are possible when no matching CDR row exists
for the tenant:
The object came from the org’s own storage. The row is still shown.
Caller and callee fall back to empty strings and render as —,
direction and duration are null, and the timestamp falls back to the
object’s last-modified time rather than a call start time. This is what a
recording with blank participants means: audio exists in storage, but no
CDR for this tenant references that call id yet — for example because the
CDR has not been written, or because it was aged out of the cdrs table
while the object survived in the bucket.
The object came from a shared bucket. Objects whose source is
shared and which have no cdrs row for this tenant are dropped
entirely. There is no proof the call belongs to the active org, and the
procedure will not surface an unattributed object across tenants. The CDR
row is the tenant proof. If a shared-bucket recording is missing from the
list, the missing piece is the CDR attribution, not the object.
Signed playback and download URLs
The grid never links directly to a storage object. When you press play or download, the console resolvesrecording_url first:
- A URL that does not start with
s3://— a bring-your-own destination — is passed through unchanged. - An
s3://URI is exchanged for a presigned GET by callingstorage.signedUrlwith the org id and the call id.
<call_id>.wav.
Filtering, paging and how the summary cards are computed
Every filter is applied server-side, over the merged set, before paging:
Rows that survive the predicate are sorted newest first by
starting_time, then sliced with offset and limit. limit defaults
to 200 and the input schema accepts 1–500.
The four cards at the top are computed by the server over the filtered
set as a whole, not over the returned page:
- Total Recordings — the count of filtered rows, the same number as
totalin the footer’s “Showing N of M recordings in range”. - Total Duration — the sum of every row’s duration, treating null as zero.
- Storage Used — the sum of every row’s
recording_size_bytes. - Avg Duration — total duration divided by the number of rows with a duration greater than zero, rounded. Rows with no duration, including unattributed bucket objects, are excluded from the divisor but still counted in Total Recordings.
The features.recording entitlement gate
The screen reads the org’s entitlement claims and checksclaims.features.recording. The list renders unless the claims are
loaded and the flag is explicitly false, in which case the screen
is replaced by an upsell panel and no recordings query result is shown.
The gate deliberately fails open in two cases:
- entitlement data has not loaded yet, and
- entitlement data is
null, meaning the entitlement service is not configured for this deployment.
Deleting a recording
Each row carries a delete control in the actions column. In the screen as shipped, that control has no handler attached: clicking it does not call a procedure, does not remove the object from the bucket, and does not change the CDR. Nothing is deleted, and the row returns on the next refetch. Treat the recordings grid as read-only until a delete procedure is wired to it.Related
- Telemetry — the
recording_urlcolumn in the CDR schema - Telephony Metrics
- Authentication — how short-lived signed access is minted