Skip to main content

Call recordings

The most sensitive data on this API: a recording is the contents of a conversation, not metadata about it.

A call is not one recording

A call that rang a queue, was answered, and was then transferred has several legs, and each answered leg is recorded separately. That is why the endpoint returns a list.

FieldMeaning
uniqueIdIdentifies the leg. Pass it to the single-leg endpoint to re-fetch just this one.
callIdThe switch's call id for that leg. On a transferred call it differs from the id in your request path — the request id names the whole journey, this one names the leg. Log it if you correlate against raw switch data.
startTimeWhen the leg started, RFC 3339 UTC.
durationSecondsThe leg's full length, including ringing.
talkTimeSecondsThe connected portion only. This is the closer match to the length of the audio.
from, toThe leg's endpoints as the switch recorded them. On an internal leg these are extensions, not phone numbers.

Picking "the" recording of a transferred call is a judgement only you can make — usually the leg with the largest talkTimeSeconds. Everything needed to make it is in the response, rather than hidden behind a server-side guess.

The url field

url is a signed link on this platform, not the storage location.

  • No Authorization header needed. Hand it to an <audio> element, a browser tab, or a transcription service directly.
  • The account is sealed into the signature. The link cannot be edited to reach another account's recording.
  • It does not expire.
  • It answers 302 to storage or streams the audio, depending on where the recording lives. Either way, just follow it.

:::danger Treat the URL as a secret It does not expire and needs no credential, so anyone who obtains it can play the conversation. Do not put it in a query string a third party logs, an email, a ticket body, or a client-side page that is cached. :::

Storage URLs are deliberately never returned. Recordings live in cloud storage for some accounts and on the PBX for others — the PBX ones have no URL at all and must be fetched through the switch. The signed link is one stable shape over both.

No recording yet

A call that exists but was not recorded returns 200 with an empty array:

{ "data": [] }

That is a normal answer — recording is opt-in per number and per user. It is distinct from 404, which means the call id is not on your account or does not exist.

Recordings are written after hangup and typically appear within a minute. If you poll, treat [] as "not yet" and retry with backoff. Do not treat it as "never".

A call id belonging to another account also returns 404, never 403 — guessing ids tells you nothing.

Two gates the scope does not open

Holding recordings:read is necessary, and not always sufficient.

1. Sensitive-content verification

If the account has turned on re-verification for sensitive content, recordings require a password re-entry within the last 15 minutes. That means a signed-in human at a browser — there is nothing an API credential can present.

While that setting is on, this endpoint returns 403 for every credential, key or token, and the message says so rather than giving a bare "access denied". Turning it off is an account decision, made in the portal.

2. The authorizing user's own permission — OAuth only

An OAuth token acts as the person who authorized it. If their role does not grant call-recording access (Call reports → allow recordings) in the portal, the token does not get it either. A scope narrows what a delegated app may do; it can never widen it beyond its user.

API keys are not affected today: a key is the account's own credential for its own recordings. That changes when owner-role enforcement ships.

Limits

  • Read-only. Deleting a recording is irreversible and restricted to the account owner in the portal. There is no API for it, deliberately — a third-party app should not be able to destroy evidence of a conversation.
  • Voicemail is not here. A voicemail is a message, not a recording of a conversation, and it is stored separately. Mailbox legs are excluded.
  • One call per request. There is no bulk endpoint. To sweep a date range, list calls in reports and fetch recordings per call id.
  • If you only need "was this recorded", the call webhooks already carry a recordingUrl on the event — no second call needed.