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.
| Field | Meaning |
|---|---|
uniqueId | Identifies the leg. Pass it to the single-leg endpoint to re-fetch just this one. |
callId | The 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. |
startTime | When the leg started, RFC 3339 UTC. |
durationSeconds | The leg's full length, including ringing. |
talkTimeSeconds | The connected portion only. This is the closer match to the length of the audio. |
from, to | The 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
Authorizationheader 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
302to 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
recordingUrlon the event — no second call needed.