How to download recording media
Get a recording download URL from a bot or recording, then read the JSON schemas for transcripts, participants, speaker timelines, and separate audio or video.
Download URLs are populated once the relevant media object has transitioned to done status. For accessing media data during recording, you can use Real-Time Endpoints
How to get a download URL
A download URL is a presigned link on a recording. Create Bot does not return the file. After the artifact's status is done, read the URL from the recording and fetch it.
- Create a bot with Create Bot. Mixed video is produced by default. Request other artifacts, such as a transcript, in
recording_config. - Wait until that media object's status is
done. Subscribe to Recording Webhooks (recording.done,transcript.done, and the other artifact events), or poll Retrieve Bot or Retrieve Recording. - Read the URL from
recordings[].media_shortcutson the bot, or frommedia_shortcutson the recording. - Fetch the URL with a normal HTTP GET. Do not send an
Authorizationheader. The URL already includes authentication, and it expires. See Do I need an Authorization header when fetching a download URL?.
Mixed video
media_shortcuts.video_mixed.data.download_url is the MP4. See Bot Recording.
Participant events
These URLs are on media_shortcuts.participant_events.data. See Meeting Participants & Events and Speaker Timelines.
| Field | What you get |
|---|---|
participants_download_url | Participant list. |
participant_events_download_url | Joins, leaves, and the other events. |
speaker_timeline_download_url | Who spoke, and when. |
Transcript
These URLs are on media_shortcuts.transcript.data. See Transcription overview and Accessing text-to-speech provider-specific fields.
| Field | What you get |
|---|---|
download_url | Normalized transcript. |
provider_data_download_url | Raw provider payload. |
Separate video and audio
These are not on media_shortcuts. Each object from List Video Separate and List Audio Separate has a data.download_url.
| Field | What you get |
|---|---|
video_separate.data.download_url | JSON index of separate video parts. Each part has its own download_url for the media file. |
audio_separate.data.download_url | JSON index of separate audio parts. Each part has its own download_url for the media file. |
See How to get Separate Videos per Participant (Async) and How to get Separate Audio per Participant (Async).
The schemas below describe the JSON those URLs return. Mixed video, and the media files linked from separate audio and video parts, are binary files and do not have a JSON schema here.
Schema Timestamps
There are two types of timestamps you'll see in the schemas below:
relative- represents the time in seconds from the start of the recording (calculated from the latestin_call_recordingbot status change event). For example, "relative": 9.73 means approximately 9.73 seconds after the recording beganabsolute- represents the exact date and time this event occurred in ISO 8601 format. For example, "absolute": "2025-07-17T00:00:09.730066Z" shows the precise moment when something occurred
[JSON] participants_download_url
participants_download_urlParticipant list. Full path: media_shortcuts.participant_events.data.participants_download_url. Where to find this URL.
[
{
"id": number, // Id of the participant in the meeting. This id is not unique across meetings.
"name": string | null, // Display name of the participant.
"is_host": boolean | null, // Whether the participant is the host of the meeting.
"platform": string | null, // Meeting platform constant. values: "desktop", "dial_in", "unknown"
"extra_data": json | null, // Extra data about the participant from the meeting platform.
"email": string | null, // Email, if participant identification is turned on
}
][JSON] participant_events_download_url
participant_events_download_urlJoins, leaves, and the other participant events. Full path: media_shortcuts.participant_events.data.participant_events_download_url. Where to find this URL.
[
{
"id": string, // Id of the participant event.
"action": string, // The action that the participant took. values: "join" | "leave" | "update" | "speech_on" | "speech_off" | "webcam_on" | "webcam_off" | "screenshare_on" | "screenshare_off" | "chat_message"
"participant": {
"id": integer, // Id of the participant in the meeting. This id is not unique across meetings.
"name": string | null, // Display name of the participant.
"is_host": boolean | null, // Whether the participant is the host of the meeting.
"platform": string | null, // Meeting platform constant
"extra_data": json | null, // Extra data about the participant from the meeting platform.
"email": string | null, // Email, if participant identification is turned on
},
"timestamp": {
"absolute": string, // ISO 8601
"relative": number, // seconds
},
"data":
{
"text": string, // The text of chat message
"to": string, // The recipient of the chat message. values: "everyone" | "only_bot"
} // populated if the action is a `chat_message`
| null
}
][JSON] speaker_timeline_download_url
speaker_timeline_download_urlWho spoke, and when. Full path: media_shortcuts.participant_events.data.speaker_timeline_download_url. Where to find this URL.
[
{
"participant": {
"id": integer, // Id of the participant in the meeting. This id is not unique across meetings.
"name": string | null, // Display name of the participant.
"is_host": boolean | null, // Whether the participant is the host of the meeting.
"platform": string | null, // Meeting platform constant
"extra_data": json | null, // Extra data about the participant from the meeting platform.
"email": string | null, // Email, if participant identification is turned on
},
"start_timestamp": {
"absolute": string, // ISO 8601
"relative": number, // seconds
},
"end_timestamp": {
"absolute": string, // ISO 8601
"relative": number, // seconds
}
}
][JSON] transcript.data.download_url
transcript.data.download_urlNormalized transcript. Full path: media_shortcuts.transcript.data.download_url. Where to find this URL.
[
{
"participant": {
"id": number, // Id of the participant in the meeting. This id is not unique across meetings.
"name": string | null, // Display name of the participant.
"is_host": boolean | null, // Whether the participant is the host of the meeting.
"platform": string | null, // Meeting platform constant
"extra_data": json | null, // Extra data about the participant from the meeting platform.
"email": string | null, // Email, if participant identification is turned on
},
"language_code": str, // The language code from the transcription provider, normalized to BCP-47.
// The simple code is .split('-')[0], and beware that some languages require
// 3-character codes (e.g. yue and haw)
"words": [
{
"text": string, // The text of the word.
"start_timestamp": {
"absolute": string, // ISO 8601, will return null for async transcription
"relative": number // seconds
},
"end_timestamp": {
"absolute": string, // ISO 8601, will return null for async transcription
"relative": number // seconds
}
}
]
}
][JSON] video_separate.data.download_url
video_separate.data.download_urlSeparate video parts. This is data.download_url on each object from List Video Separate. Each part in the array has its own download_url for the media file. Where to find this URL.
[
{
"id": string, // Id of the separate part
"participant": {
"id": integer, // Id of the participant in the meeting. This id is not unique across meetings.
"name": string | null, // Display name of the participant.
"is_host": boolean | null, // Whether the participant is the host of the meeting.
"platform": string | null, // Meeting platform constant
"extra_data": json | null, // Extra data about the participant from the meeting platform.
"email": string | null, // Email, if participant identification is turned on
},
"start_timestamp": {
"absolute": string, // ISO 8601
"relative": number, // seconds
},
"duration": number, // seconds
"type": "webcam" | "screenshare", // the type of video part
"download_url": string, // URL to download the media file from
}
][JSON] audio_separate.data.download_url
audio_separate.data.download_urlSeparate audio parts. This is data.download_url on each object from List Audio Separate. Each part in the array has its own download_url for the media file. Where to find this URL.
[
{
"id": string, // Id of the separate part
"participant": {
"id": integer, // Id of the participant in the meeting. This id is not unique across meetings.
"name": string | null, // Display name of the participant.
"is_host": boolean | null, // Whether the participant is the host of the meeting.
"platform": string | null, // Meeting platform constant
"extra_data": json | null, // Extra data about the participant from the meeting platform.
"email": string | null, // Email, if participant identification is turned on
},
"start_timestamp": {
"absolute": string, // ISO 8601
"relative": number, // seconds
},
"duration": number, // seconds
"download_url": string, // URL to download the media file from
}
][JSON] transcript.data.provider_data_download_url
transcript.data.provider_data_download_urlRaw transcript provider payload. Full path: media_shortcuts.transcript.data.provider_data_download_url. Where to find this URL.
{
"parts": [
{
"id": string,
"created_at": string, // ISO 8601
// context of audio which was the transcription source for this part
"source_audio": {
"type": "mixed" | "separate",
"offset": float, // seconds from recording start
"participant": {
"id": integer, // Id of the participant in the meeting. This id is not unique across meetings.
"name": string | null, // Display name of the participant.
"is_host": boolean | null, // Whether the participant is the host of the meeting.
"platform": string | null, // Meeting platform constant
"extra_data": json | null, // Extra data about the participant from the meeting platform.
"email": string | null, // Email, if participant identification is turned on
} | null // participant data (only available for type=separate)
},
// part type, dictates the structure of 'data' key
"type": "webhook" | "http_response" | "websocket_message",
"data":
{ payload: object } | // type=webhook,
{ url: string, status_code: integer, body: object } | // type=http_response
{ url: string, event: string, payload: object }, // type=websocket_message
}
]
}Provider data is currently not available when using:
- The
assembly_ai_async_chunkedReal-Time Transcription provider- Async transcriptions with Perfect Diarization enabled
- Recall.ai Transcription
FAQs
Do I need an Authorization header when fetching a download URL?
No - a download_url is presigned and already includes authentication. Fetch it without an Authorization header.
Updated 13 days ago
