---
title: "Recording downloads"
description: "Assemble and download session replays as MP4 files, one per page."
image: "https://help.agent.mq/og.svg"
---

> Documentation Index
> Fetch the complete documentation index at: https://help.agent.mq/llms.txt
> Use this file to discover all available pages before exploring further.

# Recording downloads

> **Note**
>
> The Recording Downloads API turns a session's recording into downloadable MP4 files, one per recorded page (tab). Assembly runs asynchronously: request the downloads, then poll for status and a short-lived download URL. To stream playback in-app instead of downloading files, use the [Session Replay API](/browsers/observability/session-replay).

Download a completed session's recording as MP4 files, one per tab. Browsers assembles them on demand after the session ends.

## Quickstart

Given a `sessionId` from a session that has ended, request the downloads, then poll until each page is `COMPLETED`.

### Node.js

```typescript
import { Browsers } from "@agentmq/browsers";

const sessionId = "<your session ID>";
const browsers = new Browsers({ apiKey: process.env.AGENTMQ_TOKEN! });

// Request one MP4 per recorded page (tab); each page starts PENDING.
await browsers.sessions.recording.downloads.create(sessionId);

// Poll until every page has finished assembling.
let downloads;
do {
  await new Promise((resolve) => setTimeout(resolve, 3000));
  ({ downloads } = await browsers.sessions.recording.downloads.list(sessionId));
} while (downloads.some((page) => page.status === "PENDING"));

for (const page of downloads) {
  console.log(page.pageId, page.status, page.downloadUrl);
}
```

### Python

```python
import os
import time

from agentmq.browsers import Browsers

session_id = "<your session ID>"
browsers = Browsers(api_key=os.environ["AGENTMQ_TOKEN"])

# Request one MP4 per recorded page (tab); each page starts PENDING.
browsers.sessions.recording.downloads.create(session_id)

# Poll until every page has finished assembling.
while True:
    downloads = browsers.sessions.recording.downloads.list(session_id).downloads
    if not any(page.status == "PENDING" for page in downloads):
        break
    time.sleep(3)

for page in downloads:
    print(page.page_id, page.status, page.download_url)
```

### cURL

```bash
# Request the downloads (each page starts PENDING).
curl -X POST https://api.agent.mq/v1/browsers/sessions/$SESSION_ID/recording/downloads \
  -H "Authorization: Bearer $AGENTMQ_TOKEN"

# Poll for status and download URLs.
curl https://api.agent.mq/v1/browsers/sessions/$SESSION_ID/recording/downloads \
  -H "Authorization: Bearer $AGENTMQ_TOKEN"
```

## How it works

Assembly of the recording is asynchronous, driven by two calls on the same path:

1. **Request the downloads (POST)**

   `POST /v1/sessions/{id}/recording/downloads` enqueues one MP4 per recorded page (tab). Assuming the session has ended and is still within its retention window, it returns `202` and marks every page `PENDING`. Re-posting re-enqueues all pages and retries any that failed, so it is safe to call again.

2. **Poll for status (GET)**

   `GET /v1/sessions/{id}/recording/downloads` returns the current state of each page. Poll it until the pages you need reach `COMPLETED` (or `FAILED`).

3. **Download the MP4**

   Each `COMPLETED` page carries a short-lived `downloadUrl`. Opening it downloads the MP4 directly and saves it as `{sessionId}-{pageId}.mp4`.

### Status values

Each page reports one of four states:

| Status          | Meaning                                                          |
| --------------- | ---------------------------------------------------------------- |
| `NOT_REQUESTED` | You haven't requested a download for the session yet.            |
| `PENDING`       | Browsers is assembling the MP4 (queued or in progress).       |
| `COMPLETED`     | The MP4 is ready and `downloadUrl` is present. |
| `FAILED`        | Assembly failed. POST again to retry the page.                   |

## Response shape

Each entry corresponds to one recorded page (tab).

```json
{
  "downloads": [
{
  "pageId": "0",
  "status": "COMPLETED",
  "downloadUrl": "https://.../recording-renditions/.../0.mp4?token=...",
  "completedAt": "2026-06-30T18:24:05.000Z"
},
{
  "pageId": "1",
  "status": "PENDING"
}
  ]
}
```

Field semantics:

* `pageId` identifies the recorded page within the session, ascending from `"0"`.
* `downloadUrl` and `completedAt` are present only when `status` is `COMPLETED`.

## Multitab

Browsers records up to 10 tabs open concurrently per session, and produces one MP4 per recorded page. Request downloads once; poll the list to collect each page's file as it finishes.

## Download URL expiration

Browsers signs each `downloadUrl`, and it expires **six hours** after the API issues it. The GET endpoint re-mints the URL on every call, so if a link expires, list the downloads again to get a fresh one.

## Availability window

You can assemble recording downloads while Browsers still retains the session's recording source, for up to **31 days** after the session ends. After that, the source is gone and the API returns `410 Gone`.

> **Note**
>
> Downloads become available only after a session **ends**. Requesting them for a running session returns `409 Conflict`.

## Rate limits

The POST endpoint is limited to **5 requests per minute** per application, since each call kicks off assembly. Back off and retry when the endpoint answers with 429.

## Recording downloads vs. Session replay

* **Recording downloads** produce standalone **MP4 files** you fetch and store (best for archiving and offline review).
* **[Session replay](/browsers/observability/session-replay)** streams the recording as **HLS** for embedded, in-app playback.

Both draw from the same underlying replay data.

## Disabling recordings

Disable recording when you create a session:

### Node.js

```typescript Node.js
const session = await browsers.sessions.create({
  browserSettings: {
    recordSession: false,
  },
});
```

### Python

```python
session = browsers.sessions.create(
    browser_settings={"record_session": False},
)
```

Sessions you create with `recordSession: false` produce no recording, so there is nothing to download. Both endpoints return `422 Unprocessable Content` with `{"message": "Recording was disabled for this session"}`. See [Session replay](/browsers/observability/session-replay#disabling-recordings).

## Troubleshooting

**GET returns `409 Conflict`:**

* The session has not ended yet. Browsers assembles downloads only after a session completes.

**GET returns `410 Gone`:**

* The recording aged out of its 31-day retention window. Browsers can no longer assemble it.

**A page stays `PENDING`:**

* Large recordings take longer to assemble. Keep polling; re-POST only if a page reports `FAILED`.

**`downloadUrl` is missing on a `COMPLETED` page:**

## Related

- **Session Inspector** — [Replay every session in the console to debug issues after the fact.](/browsers/observability/observability#video-replay)

- **Session Replay** — [Stream a session's recording as HLS for embedded, in-app playback.](/browsers/observability/session-replay)

- **Bring your own storage** — Route recording renditions to your own S3 bucket.

- **Recording downloads API** — [API reference for requesting and listing per-page MP4 downloads.](/browsers/api)

Source: https://help.agent.mq/browsers/observability/recording-downloads/index.mdx
