Skip to main content

Before you start

Use a server-side API key with recordings:read. The scope is independent of calls:read and only appears on the key-creation form once an organization administrator has enabled recordings for your organization; see Authentication and scopes for how to add the scope to a new key.

List recordings

Call GET /recordings. Results cover every recording the organization has, across both call-storage tables, ordered newest-first by recording time.
Add include=download_url to mint a signed URL for every row in the same response — the right choice when you are about to fetch many recordings in bulk and want to avoid one extra request per row. Omit it for a lighter, metadata-only page. expires_in only means something alongside include=download_url on this operation; sending it without include returns 400 validation_error instead of being silently ignored, because a setting that appears to apply but does not would be worse than an error.

Sync recordings completely

recorded_at is the call’s time, not the time its download_url-bearing row became fetchable. A recording whose URL is written well after the call — a repair job, a re-run, a manual fix — can be written after your sync cursor has already moved past it, and incremental sync will never see it again. This is a real, measured limitation, not a hypothetical one, so the contract has two layers.

Incremental sync — best-effort

Track the timestamp of your last successful sync. On the next run, call GET /recordings?created_after=<last sync time minus 24 hours> and dedupe results against what you already have by id. The 24-hour overlap absorbs the normal case — on measured production data, a recording written more than six hours after its call is vanishingly rare — but it does not absorb one repaired long after the fact. Run this as often as you like; it is cheap.

Full reconciliation — the only guarantee of completeness

Periodically walk the full cursor with no created_after at all, deduping by id against what you already have. This is the only way to guarantee you have every recording, and it is inexpensive. Measured across our full platform on 2026-09-01: 32,261 recordings divided by 100 per page is about 323 requests, comfortably under six minutes at 60 requests per minute, and every one of those requests returns metadata only — no audio bytes. That count is our total across every organization, not yours — what carries over to your own organization is the ratio (about one request per 100 recordings you have) and the fact that a full walk is metadata-only regardless of scale. Run this daily or weekly depending on how much a missed recording costs you. Do not rely on incremental sync alone and call it complete.

Fetch a URL only when you are about to use it

GET /recordings/{recording_id} always includes download_url — no include needed. If you are building a player inside your own product, request this URL when the listener presses play, not when the page listing recordings first loads. The URL defaults to a 900-second lifetime (override with expires_in, 60–3600 seconds); a page left open longer than that would otherwise hold a value that silently stops working.

Revoking access does not invalidate an issued URL

This is inherent to how a signed URL works, not an oversight: the URL itself is the credential, and nothing checks the originating key again once it has been handed out. The residual window is exactly the expires_in value the URL was minted with — at most one hour, 15 minutes by default. A customer who assumes revocation is instant for already-issued links will make the wrong security decision; plan around the expiry, not the revocation.