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

# Session replay

> Stream session replays as HLS to embed playback in your own application.

<Info>
  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](https://github.com/video-dev/hls.js) or any HLS-capable player. The same recordings power the Dashboard's [Session Inspector](/platform/browser/observability/observability).
</Info>

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](/platform/browser/getting-started/create-browser-session), fetch a replay in two calls: list the pages, then fetch the playlist for any page.

<Tabs>
  <Tab title="Node.js">
    ```typescript theme={null}
    import { Browserbase } from "@browserbasehq/sdk";

    const sessionId = "<your session ID>";
    const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });

    const meta = await bb.sessions.replays.retrieve(sessionId);
    const firstPage = meta.pages[0];

    const playlist = await bb.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);
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import os
    from browserbase import Browserbase

    session_id = "<your session ID>"
    bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])

    meta = bb.sessions.replays.retrieve(session_id)
    first_page = meta.pages[0]

    playlist = bb.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)
    ```
  </Tab>

  <Tab title="cURL">
    ```bash theme={null}
    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"
    ```
  </Tab>
</Tabs>

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](https://en.wikipedia.org/wiki/HTTP_Live_Streaming) or the spec in [RFC 8216](https://datatracker.ietf.org/doc/html/rfc8216).

## Embedding a player

Any HLS-capable player works. Pick whichever fits your stack; the playlist URL is the same in all cases.

<Warning>
  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](#recommended-integration-pattern) below for the proxy shape.
</Warning>

<Tabs>
  <Tab title="hls.js">
    [hls.js](https://github.com/video-dev/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.

    ```html theme={null}
    <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>
    ```
  </Tab>

  <Tab title="Shaka Player">
    [Shaka Player](https://github.com/shaka-project/shaka-player) is Google's open-source player and supports both HLS and DASH.

    ```html theme={null}
    <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>
    ```
  </Tab>

  <Tab title="video.js">
    [Video.js](https://videojs.com/) ships HLS support via `@videojs/http-streaming` and exposes a styled UI out of the box.

    ```html theme={null}
    <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>
    ```
  </Tab>

  <Tab title="Native HLS">
    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.

    ```html theme={null}
    <!-- src: route on your own backend; see Recommended integration pattern -->
    <video controls muted autoplay playsinline src="/replays/<sessionId>/<pageId>"></video>
    ```
  </Tab>
</Tabs>

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.

```json theme={null}
{
  "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

<Steps>
  <Step title="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.

    <Tabs>
      <Tab title="Express (Node.js)">
        ```typescript theme={null}
        import express from "express";
        import { Browserbase } from "@browserbasehq/sdk";

        const app = express();
        const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY! });

        app.get("/replays/:sessionId/:pageId", async (req, res) => {
          const { sessionId, pageId } = req.params;
          try {
            const playlist = await bb.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");
          }
        });
        ```
      </Tab>

      <Tab title="FastAPI (Python)">
        ```python theme={null}
        import os
        from browserbase import Browserbase
        from fastapi import FastAPI, Response

        app = FastAPI()
        bb = Browserbase(api_key=os.environ["BROWSERBASE_API_KEY"])

        @app.get("/replays/{session_id}/{page_id}")
        def get_replay(session_id: str, page_id: str):
            playlist = bb.sessions.replays.retrieve_page(page_id, id=session_id)
            return Response(
                content=playlist.read(),
                media_type="application/vnd.apple.mpegurl",
            )
        ```
      </Tab>
    </Tabs>

    Your frontend then points its HLS player at `/replays/<sessionId>/<pageId>` on your own origin.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>
</Steps>

This avoids double-egress through your servers while keeping the API key off the browser.

<Note>
  **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.
</Note>

## 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](/optimizations/concurrency/overview) 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](/platform/browser/observability/session-recording#disabling-video-recordings).

<Note>
  Sessions you create with `recordSession: false` produce no replay. Both endpoints return `404 Not Found` with `{"message": "Replay not found"}`. [Live View](/platform/browser/observability/session-live-view) remains available for real-time debugging.
</Note>

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

<Note>
  Questions? Email [support@browserbase.com](mailto:support@browserbase.com)
</Note>
