> ## Documentation Index
> Fetch the complete documentation index at: https://docs.anchorbrowser.io/llms.txt
> Use this file to discover all available pages before exploring further.

# P2P Download

> Receive download events in real time and fetch files directly from the browser

## What is P2P Download?

P2P downloads let you receive a real-time notification the moment a file is downloaded in your browser session, then fetch the file **directly from the active session** — no polling, no waiting for Anchor to process the file, no storage APIs required.

## How It Works

**Traditional downloads:**

> Browser downloads file → Anchor stores the file → You poll `GET /downloads` → Fetch from Anchor storage

**P2P downloads:**

> Browser downloads file → `Anchor.downloadReady` CDP event fires → You fetch directly from the browser

When a file is downloaded, the browser emits a custom CDP event (`Anchor.downloadReady`) over your existing WebSocket connection. The event includes a pre-built fetch URL that streams the file bytes directly from the browser's disk.

## Implementation

<Steps>
  <Step title="Connect to the session and listen for the CDP event">
    Connect Playwright over CDP as usual, then open a `CDPSession` on the page to listen for the `Anchor.downloadReady` event.

    <CodeGroup>
      ```typescript node.js theme={null}
      import { chromium } from "playwright";
      import AnchorBrowser from "anchorbrowser";
      import fs from "fs";

      const { ANCHOR_API_KEY } = process.env;
      const client = new AnchorBrowser({ apiKey: ANCHOR_API_KEY });

      const session = await client.sessions.create();
      const { cdp_url, id: sessionId } = session.data;

      const browser = await chromium.connectOverCDP(cdp_url);
      const page = browser.contexts()[0].pages()[0];

      const cdpSession = await page.context().newCDPSession(page);

      const downloadReady = new Promise((resolve) => {
        cdpSession.on("Anchor.downloadReady", (params) => resolve(params));
      });
      ```

      ```python python theme={null}
      import os
      from playwright.sync_api import sync_playwright
      from anchorbrowser import Anchorbrowser

      ANCHOR_API_KEY = os.getenv("ANCHOR_API_KEY")
      client = Anchorbrowser(api_key=ANCHOR_API_KEY)

      session = client.sessions.create()
      cdp_url = session.data["cdp_url"]

      with sync_playwright() as p:
          browser = p.chromium.connect_over_cdp(cdp_url)
          page = browser.contexts[0].pages[0]
          cdp_session = page.context.new_cdp_session(page)

          download_params = {}

          def on_download_ready(params):
              download_params.update(params)

          cdp_session.on("Anchor.downloadReady", on_download_ready)
      ```
    </CodeGroup>
  </Step>

  <Step title="Trigger the download">
    Use Playwright as normal to navigate and click the download link.

    <CodeGroup>
      ```typescript node.js theme={null}
      await page.goto("https://example.com/reports");
      await page.click("#download-report");
      ```

      ```python python theme={null}
      with sync_playwright() as p:
          browser = p.chromium.connect_over_cdp(cdp_url)
          page = browser.contexts[0].pages[0]
          page.goto("https://example.com/reports")
          page.click("#download-report")
      ```
    </CodeGroup>
  </Step>

  <Step title="Receive the event and fetch the file">
    Wait for the `Anchor.downloadReady` event, then use the `p2pDownloadUrl` from the event params to fetch the file.

    <CodeGroup>
      ```typescript node.js theme={null}
      // Wait for the download event
      const event = await downloadReady;

      console.log(`Download ready: ${event.suggestedFilename} (${event.size} bytes)`);

      // Fetch the file directly from the session
      const response = await fetch(
        `https://api.anchorbrowser.io${event.p2pDownloadUrl}`,
        { headers: { "anchor-api-key": ANCHOR_API_KEY } }
      );

      fs.writeFileSync(
        event.suggestedFilename,
        Buffer.from(await response.arrayBuffer())
      );

      console.log(`Saved ${event.suggestedFilename}`);
      await browser.close();
      ```

      ```python python theme={null}
      import requests, time

      # Wait for the download event (up to 15 seconds)
      timeout = 15
      start = time.time()
      while not download_params and time.time() - start < timeout:
          time.sleep(0.1)

      params = download_params
      print(f"Download ready: {params['suggestedFilename']} ({params['size']} bytes)")

      # Fetch the file directly from the session
      response = requests.get(
          f"https://api.anchorbrowser.io{params['p2pDownloadUrl']}",
          headers={"anchor-api-key": ANCHOR_API_KEY},
          stream=True,
      )

      with open(params["suggestedFilename"], "wb") as f:
          for chunk in response.iter_content(chunk_size=8192):
              f.write(chunk)

      print(f"Saved {params['suggestedFilename']}")
      ```
    </CodeGroup>
  </Step>
</Steps>

## The `Anchor.downloadReady` Event

The event is emitted on the CDP WebSocket connection as soon as the file lands on disk.

```json theme={null}
{
  "method": "Anchor.downloadReady",
  "params": {
    "localDownloadId": "4e901615-5f83-4773-a1af-773e15a19be8",
    "suggestedFilename": "report.pdf",
    "url": "https://example.com/report.pdf",
    "originUrl": "https://example.com/reports",
    "size": 1048576,
    "duration": 2300,
    "p2pDownloadUrl": "/v1/sessions/{sessionId}/downloads/{localDownloadId}/p2p"
  }
}
```

| Field               | Description                                                        |
| ------------------- | ------------------------------------------------------------------ |
| `localDownloadId`   | Unique ID for this download, valid for the lifetime of the session |
| `suggestedFilename` | The filename as suggested by the browser                           |
| `url`               | The URL the file was downloaded from                               |
| `originUrl`         | The page URL where the download was triggered                      |
| `size`              | File size in bytes                                                 |
| `duration`          | Time to complete the download in milliseconds                      |
| `p2pDownloadUrl`    | Relative URL to fetch the file — prepend your API base URL         |

## Fetch Endpoint

```
GET /v1/sessions/:session_id/downloads/:local_download_id/p2p
```

This endpoint streams the file directly from the active session. It is only available while the session is **active**. Once the session ends, use the standard [session downloads API](/api-reference/browser-sessions/list-session-downloads) to retrieve files from Anchor storage.

| Status | Meaning                                                                       |
| ------ | ----------------------------------------------------------------------------- |
| `200`  | File streamed successfully                                                    |
| `404`  | `local_download_id` not found (may have expired with the session)             |
| `410`  | Session is no longer active — use the standard `/downloads/:id/fetch` instead |

## Limitations

* The P2P fetch URL is only valid while the **session is running**. After the session ends, the file is no longer accessible via this endpoint.
* Files are still stored by Anchor in the background as a fallback. Once the session completes, they are accessible via the standard [list session downloads](/api-reference/browser-sessions/list-session-downloads) API.
* Blob and data URL downloads (files generated client-side in JavaScript) are supported.
