Skip to content

Recording downloads

Assemble and download session replays as MP4 files, one per page.

Updated View as Markdown

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:

  • 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.

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:

Navigation

Type to search…

↑↓ navigate↵ selectEsc close