Browsers records every session as a video by default. After a session completes, you can:
- Stream and embed: Stream the replay as HLS in your application, console, ticketing tool, or QA workflow.
- Download: Save the recording as an MP4 with Recording downloads.
- Inspect and debug: Replay it in the console alongside network requests for page-by-page debugging.
Quickstart
Given a sessionId from Create a Session, fetch a replay in two calls: list the pages, then fetch the playlist for any page.
import { Browsers } from "@agentmq/browsers";
const sessionId = "<your session ID>";
const browsers = new Browsers({ apiKey: process.env.AGENTMQ_TOKEN! });
const meta = await browsers.sessions.replays.retrieve(sessionId);
const firstPage = meta.pages[0];
const playlist = await browsers.sessions.replays.retrievePage(
sessionId,
firstPage.pageId,
);
const m3u8 = await playlist.text();
// The body is an HLS playlist. Each "https://..." line is a
// signed CDN segment URL valid for 6 hours; hand the whole body
// to any HLS player.
const firstSegmentUrl = m3u8
.split("\n")
.find((line) => line.startsWith("https://"));
console.log(firstSegmentUrl);import os
from agentmq.browsers import Browsers
session_id = "<your session ID>"
browsers = Browsers(api_key=os.environ["AGENTMQ_TOKEN"])
meta = browsers.sessions.replays.retrieve(session_id)
first_page = meta.pages[0]
playlist = browsers.sessions.replays.retrieve_page(
first_page.page_id, id=session_id,
)
m3u8 = playlist.read().decode("utf-8")
# The body is an HLS playlist. Each "https://..." line is a
# signed CDN segment URL valid for 6 hours; hand the whole body
# to any HLS player.
first_segment_url = next(
line for line in m3u8.splitlines() if line.startswith("https://")
)
print(first_segment_url)curl https://api.agent.mq/v1/browsers/sessions/$SESSION_ID/replays \
-H "Authorization: Bearer $AGENTMQ_TOKEN"
curl https://api.agent.mq/v1/browsers/sessions/$SESSION_ID/replays/0 \
-H "Authorization: Bearer $AGENTMQ_TOKEN"The metadata response lists each tab’s recording. The playlist response is an HLS .m3u8 document: a plain-text manifest that points at a sequence of fragmented-MP4 (.m4s) segments served from the Browsers CDN. Any HLS player fetches the manifest and streams the segments in order. For background on the format, see the HTTP Live Streaming overview on Wikipedia or the spec in RFC 8216.
Embedding a player
Any HLS-capable player works. Pick whichever fits your stack; the playlist URL is the same in all cases.
hls.js is the most common choice for cross-browser HLS playback, and the recommended path for Firefox desktop, which has no native HLS support.
<video id="replay" controls muted autoplay playsinline></video>
<script src="https://cdn.jsdelivr.net/npm/hls.js@1"></script>
<script>
const video = document.getElementById("replay");
const hls = new Hls();
// Route on your own backend that proxies the Session Replay API.
// See "Recommended integration pattern" below.
hls.loadSource("/replays/<sessionId>/<pageId>");
hls.attachMedia(video);
</script>Shaka Player is Google’s open-source player and supports both HLS and DASH.
<video id="replay" controls muted autoplay playsinline></video>
<script src="https://cdn.jsdelivr.net/npm/shaka-player@4/dist/shaka-player.compiled.min.js"></script>
<script>
const video = document.getElementById("replay");
const player = new shaka.Player();
await player.attach(video);
// Route on your own backend that proxies the Session Replay API.
// See "Recommended integration pattern" below.
await player.load("/replays/<sessionId>/<pageId>");
</script>Video.js ships HLS support via @videojs/http-streaming and exposes a styled UI out of the box.
<link href="https://cdn.jsdelivr.net/npm/video.js@8/dist/video-js.css" rel="stylesheet">
<video id="replay" class="video-js" controls muted autoplay playsinline></video>
<script src="https://cdn.jsdelivr.net/npm/video.js@8/dist/video.min.js"></script>
<script>
const player = videojs("replay");
player.src({
// Route on your own backend that proxies the Session Replay API.
// See "Recommended integration pattern" below.
src: "/replays/<sessionId>/<pageId>",
type: "application/vnd.apple.mpegurl",
});
</script>Safari and Chromium-based browsers (Chrome, Edge) play HLS natively, with no JavaScript player needed. Firefox desktop has no native HLS support; use hls.js (or another MSE-based player) there.
On Chromium, native HLS can fall through your library’s canPlayType check, so segments end up on the browser’s CORS path instead of the library’s. If you mix native and library playback, prefer Hls.isSupported() (or your library’s equivalent) to keep the fetch path consistent.
<!-- src: route on your own backend; see Recommended integration pattern -->
<video controls muted autoplay playsinline src="/replays/<sessionId>/<pageId>"></video>Browsers only autoplay when the video is muted; the muted attribute is required even for short replays. Drop autoplay and muted if you want a click-to-play UX.
The playlist body references signed segment URLs on the Browsers CDN. The browser fetches those segments directly, so your backend does not need to proxy them.
Multitab
Each recorded tab appears as its own page in the metadata response, with its own playlist URL.
{
"pages": [
{
"pageId": "0",
"url": "/v1/sessions/0a4c2f10-d7c0-4af8-9efb-a8d5c9f1b2e6/replays/0",
"startTimeMs": 0,
"endTimeMs": 121382
},
{
"pageId": "1",
"url": "/v1/sessions/0a4c2f10-d7c0-4af8-9efb-a8d5c9f1b2e6/replays/1",
"startTimeMs": 13001,
"endTimeMs": 121382
}
],
"pageCount": 2
}Field semantics:
urlis a relative path againsthttps://api.agent.mq.startTimeMsandendTimeMsare milliseconds from session start, not Unix epoch.- The API returns pages ordered by
pageIdascending.
Browsers records up to 10 tabs open concurrently per session. Tabs you open while 10 are already recording will not appear in the replay.
Recommended integration pattern
Backend fetches the playlist
Your backend calls GET /v1/sessions/{id}/replays/{pageId} with Authorization: Bearer and forwards the .m3u8 body to your frontend unchanged.
import express from "express";
import { Browsers } from "@agentmq/browsers";
const app = express();
const browsers = new Browsers({ apiKey: process.env.AGENTMQ_TOKEN! });
app.get("/replays/:sessionId/:pageId", async (req, res) => {
const { sessionId, pageId } = req.params;
try {
const playlist = await browsers.sessions.replays.retrievePage(
sessionId,
pageId,
);
res
.type("application/vnd.apple.mpegurl")
.send(await playlist.text());
} catch (err: any) {
res.status(err?.status ?? 500).send(err?.message ?? "Replay error");
}
});import os
from agentmq.browsers import Browsers
from fastapi import FastAPI, Response
app = FastAPI()
browsers = Browsers(api_key=os.environ["AGENTMQ_TOKEN"])
@app.get("/replays/{session_id}/{page_id}")
def get_replay(session_id: str, page_id: str):
playlist = browsers.sessions.replays.retrieve_page(page_id, id=session_id)
return Response(
content=playlist.read(),
media_type="application/vnd.apple.mpegurl",
)Your frontend then points its HLS player at /replays/<sessionId>/<pageId> on your own origin.
Frontend loads the playlist
Point your HLS player at the route on your backend that returns the playlist. The player parses the manifest and starts requesting segments.
Browser streams segments from the CDN
Segment URLs in the playlist are pre-signed CDN links. The browser fetches them directly; no proxying through your servers.
This avoids double-egress through your servers while keeping the API key off the browser.
Segment URL expiration
Browsers signs each playlist’s segment URLs, and they expire six hours after the API issues the playlist. Most playback sessions finish well before that. To resume playback beyond the expiry window, re-request the playlist; the API mints fresh segment URLs on every call.
Retention window
Browsers retains a session’s recording source for up to 31 days after the session ends. You can stream a replay any time within that window. After it, the source is gone and both replay endpoints return 404 Not Found with {"message": "Replay not found"} — the same response as a session that was never recorded. If you need replays past 31 days, download them as MP4 files and archive them yourself while the recording is still available.
Rate limits
Browsers rate limits the playlist endpoint (GET /v1/sessions/{id}/replays/{pageId}) to 120 requests per minute per application (sustained 2 RPS). Bursts above 2 RPS still succeed as long as the application stays under 120 requests per minute. Going over returns a 429 status code with standard rate-limit response headers; back off and retry.
Disabling recordings
The Session Replay API serves the same recordings the console plays. To skip recording for a session entirely, disable recording when you create it:
const session = await browsers.sessions.create({
browserSettings: {
recordSession: false,
},
});session = browsers.sessions.create(
browser_settings={"record_session": False},
)Troubleshooting
Player loads the playlist but no video plays:
- Some HLS players don’t auto-play after
loadedmetadata. Callvideo.play()manually. - Most browsers block autoplay unless the video is muted; set
mutedon the<video>element if you want playback to start without a click.
Segment requests start failing after several hours of playback:
- Browsers signs segment URLs, and they expire six hours after the API issues the playlist. Re-fetch the playlist to get fresh segment URLs.
Chromium plays via native HLS instead of your JavaScript player:
- Chromium browsers (Chrome, Edge) ship native HLS, which can fall through your
canPlayTypechecks. Prefer your library’s support detection (e.g.Hls.isSupported()) before the native fallback so segments go through the library’s CORS-aware fetch path.