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.
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);
}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)# 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:
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.
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).
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).
{
"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:
pageIdidentifies the recorded page within the session, ascending from"0".downloadUrlandcompletedAtare present only whenstatusisCOMPLETED.
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.
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 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:
const session = await browsers.sessions.create({
browserSettings: {
recordSession: false,
},
});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.
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: