Documentation Index
Fetch the complete documentation index at: https://docs.browserbase.com/llms.txt
Use this file to discover all available pages before exploring further.
The Session Replay API streams session replays as HLS for embedded playback. Each tab’s recording gets its own playlist; play them back with hls.js or any HLS-capable player. The same recordings power the Dashboard’s Session Inspector.
Embed session playback in your own application, dashboard, ticketing tool, or QA workflow. Browserbase records every session by default.
Quickstart
Given a sessionId from Create a Session, fetch a replay in two calls: list the pages, then fetch the playlist for any page.
const sessionId = "<your session ID>";
const apiKey = process.env.BROWSERBASE_API_KEY!;
const headers = { "x-bb-api-key": apiKey };
const meta = await fetch(
`https://api.browserbase.com/v1/sessions/${sessionId}/replays`,
{ headers },
).then((r) => r.json());
const firstPage = meta.pages[0];
const m3u8 = await fetch(
`https://api.browserbase.com/v1/sessions/${sessionId}/replays/${firstPage.pageId}`,
{ headers },
).then((r) => r.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
import requests
session_id = "<your session ID>"
api_key = os.environ["BROWSERBASE_API_KEY"]
headers = {"x-bb-api-key": api_key}
meta = requests.get(
f"https://api.browserbase.com/v1/sessions/{session_id}/replays",
headers=headers,
).json()
first_page = meta["pages"][0]
m3u8 = requests.get(
f"https://api.browserbase.com/v1/sessions/{session_id}/replays/{first_page['pageId']}",
headers=headers,
).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.
first_segment_url = next(
line for line in m3u8.splitlines() if line.startswith("https://")
)
print(first_segment_url)
curl https://api.browserbase.com/v1/sessions/$SESSION_ID/replays \
-H "x-bb-api-key: $BROWSERBASE_API_KEY"
curl https://api.browserbase.com/v1/sessions/$SESSION_ID/replays/0 \
-H "x-bb-api-key: $BROWSERBASE_API_KEY"
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 Browserbase 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.
In each snippet below, the src / loadSource URL points at a route on your own backend — not at api.browserbase.com directly. Calling the Session Replay API from the browser would expose your BROWSERBASE_API_KEY to every viewer. See Recommended integration pattern below for the proxy shape.
hls.js
Shaka Player
video.js
Native HLS
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 Browserbase 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:
url is a relative path against https://api.browserbase.com.
startTimeMs and endTimeMs are milliseconds from session start, not Unix epoch.
- The API returns pages ordered by
pageId ascending.
Browserbase 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 x-bb-api-key and forwards the .m3u8 body to your frontend unchanged. Express (Node.js)
FastAPI (Python)
import express from "express";
const app = express();
app.get("/replays/:sessionId/:pageId", async (req, res) => {
const { sessionId, pageId } = req.params;
const upstream = await fetch(
`https://api.browserbase.com/v1/sessions/${sessionId}/replays/${pageId}`,
{ headers: { "x-bb-api-key": process.env.BROWSERBASE_API_KEY! } },
);
if (!upstream.ok) {
res.status(upstream.status).send(await upstream.text());
return;
}
res
.type("application/vnd.apple.mpegurl")
.send(await upstream.text());
});
import os
import requests
from fastapi import FastAPI, Response
app = FastAPI()
@app.get("/replays/{session_id}/{page_id}")
def get_replay(session_id: str, page_id: str):
upstream = requests.get(
f"https://api.browserbase.com/v1/sessions/{session_id}/replays/{page_id}",
headers={"x-bb-api-key": os.environ["BROWSERBASE_API_KEY"]},
)
return Response(
content=upstream.content,
status_code=upstream.status_code,
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.
Alternative: skip the proxy. If your backend renders the playback page server-side, you can fetch the playlist there, embed the .m3u8 body in the page (or hand it to the player as a Blob URL), and skip the proxy route entirely. Segment URLs in the body are signed and work directly from the browser regardless of how the body got there.
Segment URL expiration
Browserbase 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.
Rate limits
Browserbase rate limits the playlist endpoint (GET /v1/sessions/{id}/replays/{pageId}) to 120 requests per minute per project (sustained 2 RPS). Bursts above 2 RPS still succeed as long as your project stays under 120 requests per minute. Going over returns a 429 status code with standard rate-limit response headers; see Concurrency & Rate Limits for the retry pattern.
Disabling recordings
The Session Replay API serves the same recordings the Dashboard plays. To skip recording for a session entirely, set recordSession to false when you create it; see Session Recording.
Sessions you create with recordSession: false produce no replay. Both endpoints return 404 Not Found with {"message": "Replay not found"}. Live View remains available for real-time debugging.
Troubleshooting
Player loads the playlist but no video plays:
- Some HLS players don’t auto-play after
loadedmetadata. Call video.play() manually.
- Most browsers block autoplay unless the video is muted; set
muted on the <video> element if you want playback to start without a click.
Segment requests start failing after several hours of playback:
- Browserbase 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
canPlayType checks. 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.