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

# File Downloads

> Save and retrieve files downloaded during browser sessions

Hyperbrowser makes it easy to download files during your browser sessions. The files you download get stored securely in our cloud infrastructure as a zip file. You can then retrieve them using a simple API call.

<Note>
  **Self-Hosted Hyperbrowser**: For some instances of self-hosted Hyperbrowser, downloads will be available at `/tmp/<sessionId>/downloads` on the host machine.
</Note>

## How to Download Files

1. Create a new Hyperbrowser session and set the `saveDownloads` param to `true`
2. Connect to the session using your preferred automation framework (like Puppeteer or Playwright)
3. Set the download location in your code
4. Download files
5. Retrieve the download zip URL from Sessions API

## Retrieving Downloads in Sessions

<Tabs>
  <Tab title="Playwright">
    <CodeGroup>
      ```typescript Node.js theme={null}
      import { chromium } from "playwright-core";
      import { Hyperbrowser } from "@hyperbrowser/sdk";
      import { config } from "dotenv";

      config();

      const client = new Hyperbrowser({
        apiKey: process.env.HYPERBROWSER_API_KEY,
      });

      async function sleep(ms) {
        return new Promise((resolve) => setTimeout(resolve, ms));
      }

      async function waitForDownload(sessionId, timeout = 15_000) {
        const maxRetries = timeout / 1000;
        let retries = 0;
        while (retries < maxRetries) {
          console.log(
            `Waiting for download zip to be ready... (${retries + 1}/${maxRetries})`
          );
          const downloadsResponse = await client.sessions.getDownloadsURL(sessionId);
          if (
            downloadsResponse.status === "completed" ||
            downloadsResponse.status === "failed"
          ) {
            return downloadsResponse;
          }
          await sleep(1000);
          retries++;
        }
        throw new Error(`Download zip not ready after ${timeout}ms`);
      }

      async function main() {
        const session = await client.sessions.create({
          saveDownloads: true,
        });
        console.log("Session created:", session.id);

        try {
          const browser = await chromium.connectOverCDP(session.wsEndpoint);
          const defaultContext = browser.contexts()[0];
          const page = defaultContext.pages()[0];

          const cdp = await browser.newBrowserCDPSession();
          await cdp.send("Browser.setDownloadBehavior", {
            behavior: "allow",
            downloadPath: "/tmp/downloads",
            eventsEnabled: true,
          });

          await page.goto("https://browser-tests-alpha.vercel.app/api/download-test");

          // Download file from the page
          const downloadPromise = page.waitForEvent("download");
          await page.getByRole("link", { name: "Download File" }).click();
          const download = await downloadPromise;

          // Wait for the download to complete
          await download.path();
          console.log("Downloaded file:", download.suggestedFilename());

          await client.sessions.stop(session.id);

          await sleep(3000);

          // Wait for the zipped downloads to be uploaded to our storage
          const downloadsResponse = await waitForDownload(session.id);

          console.log("downloadsResponse", downloadsResponse);
        } catch (err) {
          console.error(`Encountered error: ${err}`);
        } finally {
          await client.sessions.stop(session.id);
        }
      }

      main().catch(console.error);
      ```

      ```python Python theme={null}
      import asyncio
      from hyperbrowser import AsyncHyperbrowser
      from hyperbrowser.models import CreateSessionParams
      from playwright.async_api import async_playwright
      from dotenv import load_dotenv
      import os

      load_dotenv()

      client = AsyncHyperbrowser(api_key=os.getenv("HYPERBROWSER_API_KEY"))


      async def waitForDownload(sessionId, timeout=15_000):
          maxRetries = timeout // 1000
          retries = 0
          while retries < maxRetries:
              print(f"Waiting for download zip to be ready... ({retries + 1}/{maxRetries})")
              downloadsResponse = await client.sessions.get_downloads_url(sessionId)
              if (
                  downloadsResponse.status == "completed"
                  or downloadsResponse.status == "failed"
              ):
                  return downloadsResponse
              await asyncio.sleep(1000)
              retries += 1
          raise Exception(f"Download zip not ready after {timeout}ms")


      async def main():
          session = await client.sessions.create(CreateSessionParams(save_downloads=True))
          print(f"Session created: {session.id}")

          try:
              async with async_playwright() as p:
                  browser = await p.chromium.connect_over_cdp(session.ws_endpoint)
                  default_context = browser.contexts[0]
                  page = default_context.pages[0]

                  cdp = await browser.new_browser_cdp_session()
                  await cdp.send(
                      "Browser.setDownloadBehavior",
                      {
                          "behavior": "allow",
                          "downloadPath": "/tmp/downloads",
                          "eventsEnabled": True,
                      },
                  )

                  # Navigate to a website
                  await page.goto("https://browser-tests-alpha.vercel.app/api/download-test")

                  # Start listening for download events before clicking
                  async with page.expect_download() as download_info:
                      await page.get_by_role("link", name="Download File").click()
                      download = await download_info.value

                  # Wait for the download to complete
                  await download.path()
                  print("Downloaded file:", download.suggested_filename)

                  # Stop the session and wait a few seconds to check downloads status
                  await client.sessions.stop(session.id)

              print("Session stopped, waiting a few seconds to check downloads status.")
              await asyncio.sleep(3)

              downloads_response = await waitForDownload(session.id)

              print("downloads_response\n", downloads_response.model_dump_json(indent=2))
          except Exception as e:
              print(f"Error: {e}")
          finally:
              await client.sessions.stop(session.id)


      if __name__ == "__main__":
          asyncio.run(main())
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Puppeteer">
    <CodeGroup>
      ```typescript Node.js theme={null}
      import { connect } from "puppeteer-core";
      import { Hyperbrowser } from "@hyperbrowser/sdk";
      import { config } from "dotenv";

      config();

      const client = new Hyperbrowser({
        apiKey: process.env.HYPERBROWSER_API_KEY,
      });

      async function sleep(ms) {
        return new Promise((resolve) => setTimeout(resolve, ms));
      }

      async function waitForDownload(sessionId, timeout = 15_000) {
        const maxRetries = timeout / 1000;
        let retries = 0;
        while (retries < maxRetries) {
          console.log(
            `Waiting for download zip to be ready... (${retries + 1}/${maxRetries})`
          );
          const downloadsResponse = await client.sessions.getDownloadsURL(sessionId);
          if (
            downloadsResponse.status === "completed" ||
            downloadsResponse.status === "failed"
          ) {
            return downloadsResponse;
          }
          await sleep(1000);
          retries++;
        }
        throw new Error(`Download zip not ready after ${timeout}ms`);
      }

      async function main() {
        const session = await client.sessions.create({
          saveDownloads: true,
        });
        console.log("Session created:", session.id);

        try {
          const browser = await connect({
            browserWSEndpoint: session.wsEndpoint,
            defaultViewport: null,
          });
          const defaultContext = browser.defaultBrowserContext();
          const page = (await defaultContext.pages())[0];

          const cdp = await browser.target().createCDPSession();
          await cdp.send("Browser.setDownloadBehavior", {
            behavior: "allow",
            downloadPath: "/tmp/downloads",
            eventsEnabled: true,
          });

          await page.goto("https://browser-tests-alpha.vercel.app/api/download-test");

          // Download file from the page
          // Set up download listener
          cdp.on("Browser.downloadWillBegin", (event) => {
            console.log("Download started:", event.suggestedFilename);
          });

          // Create a promise that resolves when the download is complete
          const downloadPromise = new Promise((resolve) => {
            cdp.on("Browser.downloadProgress", (event) => {
              if (event.state === "completed" || event.state === "canceled") {
                resolve("Done");
              }
            });
          });

          // Start the download and wait for it to complete
          await Promise.all([downloadPromise, page.locator("#download").click()]);

          await client.sessions.stop(session.id);

          await sleep(3000);

          // Wait for the zipped downloads to be uploaded to our storage
          const downloadsResponse = await waitForDownload(session.id);

          console.log("downloadsResponse", downloadsResponse);
        } catch (err) {
          console.error(`Encountered error: ${err}`);
        } finally {
          await client.sessions.stop(session.id);
        }
      }

      main().catch(console.error);
      ```

      ```python Python theme={null}
      import os
      import asyncio
      from pyppeteer import connect
      from hyperbrowser import AsyncHyperbrowser
      from hyperbrowser.models import CreateSessionParams
      from dotenv import load_dotenv

      load_dotenv()

      client = AsyncHyperbrowser(api_key=os.getenv("HYPERBROWSER_API_KEY"))


      async def waitForDownload(sessionId, timeout=15_000):
          maxRetries = timeout // 1000
          retries = 0
          while retries < maxRetries:
              print(f"Waiting for download zip to be ready... ({retries + 1}/{maxRetries})")
              downloadsResponse = await client.sessions.get_downloads_url(sessionId)
              if (
                  downloadsResponse.status == "completed"
                  or downloadsResponse.status == "failed"
              ):
                  return downloadsResponse
              await asyncio.sleep(1000)
              retries += 1
          raise Exception(f"Download zip not ready after {timeout}ms")


      async def main():
          # Create a session with downloads enabled
          session = await client.sessions.create(CreateSessionParams(save_downloads=True))
          print(f"Session created: {session.id}")

          try:
              # Connect to the browser
              browser = await connect(
                  browserWSEndpoint=session.ws_endpoint, defaultViewport=None
              )
              defaultContext = browser._defaultContext

              # Get the first page
              page = await defaultContext.newPage()

              # Get the CDP client
              cdp_client = page._client

              # Configure download behavior
              await cdp_client.send(
                  "Browser.setDownloadBehavior",
                  {
                      "behavior": "allow",
                      "downloadPath": "/tmp/downloads",
                      "eventsEnabled": True,
                  },
              )

              # Navigate to the test page
              await page.goto("https://browser-tests-alpha.vercel.app/api/download-test")

              # Set up download event handlers
              download_completed = asyncio.Event()

              def on_download_begin(event):
                  print(f"Download started: {event.get('suggestedFilename', 'unknown')}")

              def on_download_progress(event):
                  if event.get("state") in ["completed", "canceled"]:
                      download_completed.set()

              cdp_client.on("Browser.downloadWillBegin", on_download_begin)
              cdp_client.on("Browser.downloadProgress", on_download_progress)

              # Start the download by clicking the button
              download_task = asyncio.create_task(download_completed.wait())
              await page.click("#download")

              # Add a timeout to prevent hanging indefinitely
              try:
                  await asyncio.wait_for(download_task, timeout=30)
                  print("Download completed")
              except asyncio.TimeoutError:
                  print("Download timed out after 30 seconds")

              await client.sessions.stop(session.id)

              await asyncio.sleep(3)

              # Wait for downloads to be processed and uploaded
              downloads_response = await waitForDownload(session.id)

              print("downloadsResponse", downloads_response)
          except Exception as err:
              print(f"Encountered error: {err}")
          finally:
              await client.sessions.stop(session.id)


      if __name__ == "__main__":
          asyncio.run(main())
      ```
    </CodeGroup>
  </Tab>
</Tabs>

<Note>
  The downloads zip is synced in real-time to files that are downloaded during the session so you can retrieve it before the session stops as well.
</Note>

<Note>
  By default, most PDF urls opened in the browser will open in a PDF viewer in the browser rather being downloaded. To force all PDFs to be downloaded, enable the `enableAlwaysOpenPdfExternally`/`enable_always_open_pdf_externally` parameter when creating the session.
</Note>

## Retrieving Downloads with API

After your session completes and files are downloaded, you can retrieve them using the Downloads API:

1. **Get the session ID** - Note the `id` of the session that downloaded files
2. **Call the API** - Use the [Get Session Downloads URL](/api-reference/get-session-downloads-url) endpoint or SDK method `getDownloadsURL()`
3. **Poll for completion** - Check the status until it's `completed`
4. **Download the zip** - Use the `downloadsUrl` to download your files

### Response Format

The API returns a response with the following structure:

```typescript theme={null}
{
  status: "not_enabled" | "pending" | "in_progress" | "completed" | "failed"
  downloadsUrl: string | null
  error?: string | null
}
```

### Status Values

| Status        | Description                                                                         |
| ------------- | ----------------------------------------------------------------------------------- |
| `not_enabled` | The `saveDownloads` parameter was not set to `true` when creating the session       |
| `pending`     | Files are queued for processing or no files have been downloaded yet in the session |
| `in_progress` | Zip file is being created and uploaded to storage                                   |
| `completed`   | Download zip is ready - `downloadsUrl` contains the download link                   |
| `failed`      | Processing failed - check `error` field for details                                 |

<Note>
  The download zip URL is temporary and will expire according to your plan's data retention policy. Download the file promptly after retrieval.
</Note>

## Using With AI Agents

Similarly as above, you can get the downloads zip of files that were downloaded during the session used by an AI Agent. We just need to create a session with `saveDownloads` set to `true` and then pass in that session ID. Here is an example of doing it with OpenAI CUA.

<CodeGroup>
  ```typescript Node.js theme={null}
  import { Hyperbrowser } from "@hyperbrowser/sdk";
  import { config } from "dotenv";

  config();

  const client = new Hyperbrowser({
    apiKey: process.env.HYPERBROWSER_API_KEY,
  });

  async function sleep(ms: number) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  async function waitForDownload(sessionId, timeout = 15_000) {
    const maxRetries = timeout / 1000;
    let retries = 0;
    while (retries < maxRetries) {
      console.log(
        `Waiting for download zip to be ready... (${retries + 1}/${maxRetries})`
      );
      const downloadsResponse = await client.sessions.getDownloadsURL(sessionId);
      if (
        downloadsResponse.status === "completed" ||
        downloadsResponse.status === "failed"
      ) {
        return downloadsResponse;
      }
      await sleep(1000);
      retries++;
    }
    throw new Error(`Download zip not ready after ${timeout}ms`);
  }

  async function main() {
    const session = await client.sessions.create({
      saveDownloads: true,
    });
    console.log("Session created:", session.id);

    try {
      const resp = await client.agents.cua.startAndWait({
        task: "1. Go to this site https://browser-tests-alpha.vercel.app/api/download-test. 2. Click on the Download File link once, then end the task. Do not wait or double check the download, just end the task.",
        sessionId: session.id,
      });

      console.log("Status:", resp.status);
      console.log("Final result:", resp.data?.finalResult);

      await sleep(3000);

      // Wait for the zipped downloads to be uploaded to our storage
      const downloadsResponse = await waitForDownload(session.id);

      console.log("downloadsResponse", downloadsResponse);
    } catch (err) {
      console.error(`Encountered error: ${err}`);
    } finally {
      await client.sessions.stop(session.id);
    }
  }

  main().catch(console.error);
  ```

  ```python Python theme={null}
  from time import sleep
  from hyperbrowser import Hyperbrowser
  from hyperbrowser.models import CreateSessionParams, StartCuaTaskParams
  from dotenv import load_dotenv
  import os

  load_dotenv()

  client = Hyperbrowser(api_key=os.getenv("HYPERBROWSER_API_KEY"))


  def waitForDownload(sessionId, timeout=15_000):
      maxRetries = timeout // 1000
      retries = 0
      while retries < maxRetries:
          print(f"Waiting for download zip to be ready... ({retries + 1}/{maxRetries})")
          downloadsResponse = client.sessions.get_downloads_url(sessionId)
          if (
              downloadsResponse.status == "completed"
              or downloadsResponse.status == "failed"
          ):
              return downloadsResponse
          sleep(1000)
          retries += 1
      raise Exception(f"Download zip not ready after {timeout}ms")


  def main():
      session = client.sessions.create(CreateSessionParams(save_downloads=True))
      print(f"Session created: {session.id}")

      try:
          resp = client.agents.cua.start_and_wait(
              StartCuaTaskParams(
                  task="1. Go to this site https://browser-tests-alpha.vercel.app/api/download-test. 2. Click on the Download File link once, then end the task. Do not wait or double check the download, just end the task.",
                  session_id=session.id,
              )
          )

          print("Status:", resp.status)
          print("Final Result:", resp.data.final_result)

          sleep(3)

          downloads_response = waitForDownload(session.id)

          print("downloads_response", downloads_response.model_dump_json(indent=2))
      except Exception as e:
          print(f"Error: {e}")
      finally:
          client.sessions.stop(session.id)


  if __name__ == "__main__":
      main()
  ```
</CodeGroup>

<Note>
  Check out the [Agents Docs](/agents/openai-cua) for more information on how to use AI Agents with Hyperbrowser.
</Note>

## Storage and Retention

Downloaded zip files are stored securely in Hyperbrowser's cloud infrastructure and are retained according to your plan's data retention policy.

## Next Steps

Now that you've configured your sessions, start using them to automate tasks:

<CardGroup cols={2}>
  <Card title="Web Scraping" icon="code" href="/web-scraping/scrape">
    Extract data from websites
  </Card>

  <Card title="AI Agents" icon="robot" href="/agents/browser-use">
    Use AI to automate browser tasks
  </Card>
</CardGroup>
