---
title: "Downloads"
description: "Triggering and retrieving downloaded files"
image: "https://help.agent.mq/og.svg"
---

> Documentation Index
> Fetch the complete documentation index at: https://help.agent.mq/llms.txt
> Use this file to discover all available pages before exploring further.

# Downloads

Unlike screenshots saved locally, files downloaded during browser automation are stored in Browsers' cloud storage. Retrieve them using the Browsers API.

A common use case for headless browsers is downloading files from web pages. Browsers syncs every downloaded file to cloud storage and appends a Unix timestamp to avoid naming conflicts (e.g., `sample.pdf` becomes `sample-1719265797164.pdf`).

## Triggering downloads

First, trigger a download in your browser automation:

1. [Create a browser session](/browsers/getting-started/create-browser-session) and get the session ID
2. [Connect to the session](/browsers/getting-started/using-browser-session) using your preferred framework
3. Configure your library's downloads location
4. Perform the download action in your automation script

### Node.js

```typescript Playwright
  import { chromium } from "playwright-core";
  import { Browsers } from "@agentmq/browsers";

  (async () => {
    const browsers = new Browsers({ apiKey: process.env.AGENTMQ_TOKEN! });
    const session = await browsers.sessions.create();

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

    // Required to avoid playwright overriding location
    const client = await defaultContext.newCDPSession(page);
    await client.send("Browser.setDownloadBehavior", {
      behavior: "allow",
      downloadPath: "downloads",
      eventsEnabled: true,
    });

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

    const [download] = await Promise.all([
      page.waitForEvent("download"),
      page.locator("#download").click(),
    ]);

    let downloadError = await download.failure();
    if (downloadError !== null) {
      console.log("Error happened on download:", downloadError);
      throw new Error(downloadError);
    }

    // Store the session ID to retrieve downloads later
    console.log("Download completed. Session ID:", session.id);

    await page.close();
    await browser.close();
  })().catch((error) => console.error(error.message));
```

```typescript Puppeteer
  import puppeteer from "puppeteer-core";
  import { Browsers } from "@agentmq/browsers";

  (async () => {
    const browsers = new Browsers({ apiKey: process.env.AGENTMQ_TOKEN! });
    const session = await browsers.sessions.create();

    const browser = await puppeteer.connect({
      browserWSEndpoint: session.connectUrl,
    });
    const pages = await browser.pages();
    const page = pages[0];

    const client = await page.createCDPSession();
    await client.send("Browser.setDownloadBehavior", {
      behavior: "allow",
      downloadPath: "downloads",
      eventsEnabled: true,
    });

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

    const [downloadPath] = await Promise.all([
      // Puppeteer doesn't have a download event, so we need to use the CDP session to listen for the download progress
      new Promise<string>(resolve => {
        client.on('Browser.downloadProgress', event => {
          if (event.state === 'completed') {
            resolve(event.guid);
          }
        });
      }),
      page.locator("#download").click(),
    ]);

    // Store the session ID to retrieve downloads later
    console.log("Download completed. Session ID:", session.id);

    await page.close();
    await browser.close();
  })().catch((error) => console.error(error.message));
```

```typescript Selenium
  import http from "http";
  import { Builder, By } from "selenium-webdriver";
  import { Browsers } from "@agentmq/browsers";

  (async () => {
    const browsers = new Browsers({ apiKey: process.env.AGENTMQ_TOKEN! });
    const session = await browsers.sessions.create();

    // Create a custom connection with the session details
    const customHttpAgent = new http.Agent({});
    (customHttpAgent as any).addRequest = (req: any, options: any) => {
      req.setHeader("x-browsers-signing-key", session.signingKey);
      (http.Agent.prototype as any).addRequest.call(customHttpAgent, req, options);
    };

    const driver = new Builder()
      .forBrowser("chrome")
      .usingHttpAgent(customHttpAgent)
      .usingServer(
        session.seleniumRemoteUrl
      )
      .build();

    // Head to the test downloads page
    await driver.get("https://browser-tests-alpha.vercel.app/api/download-test");

    // Click the download button
    console.log("Downloading file to the remote browser...");
    await driver.findElement(By.id("download")).click();

    // Store the session ID to retrieve downloads later
    console.log("Download completed. Session ID:", session.id);

    // Clean up
    await driver.quit();
  })().catch((error) => console.error(error.message));
```

### Python

```python Playwright
  from playwright.sync_api import sync_playwright
  from agentmq.browsers import Browsers
  import os

  browsers = Browsers(api_key=os.environ["AGENTMQ_TOKEN"])
  session = browsers.sessions.create()
  print(f"Session ID: {session.id}")

  with sync_playwright() as playwright: # Use the session ID to connect to the browser
      browser = playwright.chromium.connect_over_cdp(session.connect_url)

      # Required to avoid playwright overriding the location
      cdp_session = browser.new_browser_cdp_session()
      cdp_session.send(
          "Browser.setDownloadBehavior",
          {
              "behavior": "allow",
              "downloadPath": "downloads",
              "eventsEnabled": True,
          },
      )

      context = browser.contexts[0]
      page = context.pages[0]

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

      # Click the download button
      print("Downloading file to the remote browser...")
      page.get_by_role("link", name="Download File").click()

      # Clean up
      page.close()
      browser.close()

  print("Download completed. Use the session ID to retrieve the files.")

```

```python Selenium
  from selenium import webdriver
  from selenium.webdriver.common.by import By
  from selenium.webdriver.remote.remote_connection import RemoteConnection
  from agentmq.browsers import Browsers
  import os

  browsers = Browsers(api_key=os.environ["AGENTMQ_TOKEN"])
  session = browsers.sessions.create()
  print(f"Session ID: {session.id}")

  # Create a custom connection with the session details
  class CustomRemoteConnection(RemoteConnection):
      def get_remote_connection_headers(self, parsed_url, keep_alive=False):
          headers = super().get_remote_connection_headers(parsed_url, keep_alive)
          headers["Authorization"] = os.environ["AGENTMQ_TOKEN"]
          headers["session-id"] = session.id
          return headers

  custom_conn = CustomRemoteConnection(session.selenium_remote_url)
  driver = webdriver.Remote(
      command_executor=custom_conn,
      options=webdriver.ChromeOptions()
  )

  # Head to the test downloads page
  driver.get("https://browser-tests-alpha.vercel.app/api/download-test")

  # Click the download button
  print("Downloading file to the remote browser...")
  driver.find_element(By.ID, "download").click()

  # Clean up
  driver.quit()

  print("Download completed. Use the session ID to retrieve the files.")
```

> **Caution**
>
> **Critical: `setDownloadBehavior` Configuration**
>
> When using Playwright or Puppeteer, you must call `Browser.setDownloadBehavior` via CDP to ensure downloads are synced to Browsers' storage. Pay special attention to the `downloadPath` parameter. It must be set to `"downloads"` exactly as shown in the examples above.
>
> Common misconfiguration issues:
>
> * Using an absolute path (e.g., `/tmp/downloads`) instead of `"downloads"`
> * Omitting the `setDownloadBehavior` call entirely
> * Setting `behavior` to something other than `"allow"`
>
> Without proper configuration, your downloads won't be available for retrieval.

Opening a PDF URL in a browser session also triggers a download to Browsers' cloud storage. To view the PDF instead of downloading it, configure your browser settings as shown [here](/browsers/files/screenshots#view-a-pdf-in-browser).

## Retrieving downloaded files

After triggering downloads, retrieve them using the [Downloads API](/browsers/api). The API provides granular access to individual downloaded files. You can list, filter, retrieve, and delete downloads.

Filenames don't include the timestamp suffix Browsers adds during storage.

> **Note**
>
> Files sync in real time, but large downloads may not be immediately available
> through the `/downloads` endpoint. The code below includes retry logic to
> handle this.

### List downloads

List all downloads for a session with optional filtering by filename, MIME type, file size, and creation time.

### Node.js

```typescript Node
import { writeFileSync } from "node:fs";

const API_KEY = process.env.AGENTMQ_TOKEN!;

async function saveDownloadsOnDisk(sessionId: string, retryForSeconds: number) {
  const endTime = Date.now() + retryForSeconds * 1000;

  while (Date.now() < endTime) {
    try {
      // List individual downloads for the session
      const listResponse = await fetch(
        `https://api.agent.mq/v1/browsers/downloads?sessionId=${sessionId}`,
        { headers: { "Authorization": API_KEY } }
      );
      const { downloads, total } = await listResponse.json();

      if (total > 0) {
        console.log(`Found ${total} download(s)`);

        for (const download of downloads) {
          // Download each file individually
          const fileResponse = await fetch(
            `https://api.agent.mq/v1/browsers/downloads/${download.id}`,
            {
              headers: {
                "Authorization": API_KEY,
                Accept: "application/octet-stream",
              },
            }
          );
          const buffer = Buffer.from(await fileResponse.arrayBuffer());
          writeFileSync(download.filename, buffer);
          console.log(`Saved: ${download.filename} (${download.size} bytes)`);
        }
        return;
      }
    } catch (e) {
      console.error("Error fetching downloads:", e);
      throw e;
    }

    // Wait 2 seconds before retrying
    await new Promise((resolve) => setTimeout(resolve, 2000));
  }

  throw new Error("No downloads found within the retry period");
}

(async () => {
  // Use the session ID from your browser automation to retrieve downloads
  const sessionId = "your-session-id";
  await saveDownloadsOnDisk(sessionId, 20); // wait up to 20s
  console.log("Downloads complete");
})().catch((error) => {
  console.error("Download failed:", error);
});
```

### Python

```python Python
import os
import time
import requests

API_KEY = os.environ["AGENTMQ_TOKEN"]

def save_downloads_on_disk(session_id: str, retry_seconds: int = 20):
    """
    List and download individual files from a session.
    Retries for the specified number of seconds if no downloads are found.

    :param session_id: The session ID from your browser automation
    :param retry_seconds: How long to retry if no downloads are found
    """
    end_time = time.time() + retry_seconds

    while time.time() < end_time:
        try:
            # List individual downloads for the session
            list_response = requests.get(
                "https://api.agent.mq/v1/browsers/downloads",
                params={"sessionId": session_id},
                headers={"Authorization": API_KEY},
            )
            data = list_response.json()

            if data["total"] > 0:
                print(f"Found {data['total']} download(s)")

                for download in data["downloads"]:
                    # Download each file individually
                    file_response = requests.get(
                        f"https://api.agent.mq/v1/browsers/downloads/{download['id']}",
                        headers={
                            "Authorization": API_KEY,
                            "Accept": "application/octet-stream",
                        },
                    )
                    with open(download["filename"], "wb") as f:
                        f.write(file_response.content)
                    print(f"Saved: {download['filename']} ({download['size']} bytes)")
                return
        except Exception as e:
            print(f"Error fetching downloads: {e}")
            raise

        time.sleep(2)  # Wait 2 seconds before retrying

    raise TimeoutError("No downloads found within the retry period")

# Use the session ID from your browser automation to retrieve downloads
session_id = "your-session-id"
try:
    save_downloads_on_disk(session_id)
    print("Downloads complete")
except Exception as e:
    print(f"Failed to retrieve downloads: {e}")
```

### Filtering options

| Parameter       | Type   | Description                                                |
| --------------- | ------ | ---------------------------------------------------------- |
| `sessionId`     | string | **Required.** The session ID to list downloads for.        |
| `filename`      | string | Filter by exact filename match.                            |
| `mimeType`      | string | Filter by MIME type (e.g., `application/pdf`).             |
| `minSize`       | number | Minimum file size in bytes.                                |
| `maxSize`       | number | Maximum file size in bytes.                                |
| `createdAfter`  | string | Filter downloads created after this timestamp (ISO 8601).  |
| `createdBefore` | string | Filter downloads created before this timestamp (ISO 8601). |
| `limit`         | number | Maximum results to return (1-100, default: 20).            |
| `offset`        | number | Number of results to skip for pagination.                  |

**Example with filters:**

### Node.js

```typescript Node
const API_KEY = process.env.AGENTMQ_TOKEN!;
const sessionId = "your-session-id";

// Find PDF files larger than 1KB created in the last hour
const params = new URLSearchParams({
  sessionId,
  mimeType: "application/pdf",
  minSize: "1024",
  createdAfter: new Date(Date.now() - 60 * 60 * 1000).toISOString(),
  limit: "10",
});

const response = await fetch(
  `https://api.agent.mq/v1/browsers/downloads?${params}`,
  {
    headers: { "Authorization": API_KEY },
  }
);

const { downloads } = await response.json();
console.log(`Found ${downloads.length} matching PDFs`);
```

### Python

```python Python
import os
from datetime import datetime, timedelta
import requests

API_KEY = os.environ["AGENTMQ_TOKEN"]
session_id = "your-session-id"

# Find PDF files larger than 1KB created in the last hour
response = requests.get(
    "https://api.agent.mq/v1/browsers/downloads",
    params={
        "sessionId": session_id,
        "mimeType": "application/pdf",
        "minSize": 1024,
        "createdAfter": (datetime.now() - timedelta(hours=1)).isoformat(),
        "limit": 10,
    },
    headers={"Authorization": API_KEY}
)

downloads = response.json()["downloads"]
print(f"Found {len(downloads)} matching PDFs")
```

### Get a download

Retrieve metadata or file content for a specific download. Use `Accept: application/json` for metadata, or `Accept: application/octet-stream` to download the file (default if no Accept header is provided).

### Node.js

```typescript Node
import { writeFileSync } from "node:fs";

const API_KEY = process.env.AGENTMQ_TOKEN!;
const downloadId = "download-uuid";

// Get metadata
const metadataResponse = await fetch(
  `https://api.agent.mq/v1/browsers/downloads/${downloadId}`,
  {
    headers: {
      "Authorization": API_KEY,
      Accept: "application/json",
    },
  }
);
const metadata = await metadataResponse.json();
console.log(`File: ${metadata.filename} (${metadata.size} bytes)`);

// Download file content
const fileResponse = await fetch(
  `https://api.agent.mq/v1/browsers/downloads/${downloadId}`,
  {
    headers: {
      "Authorization": API_KEY,
      Accept: "application/octet-stream",
    },
  }
);
const buffer = Buffer.from(await fileResponse.arrayBuffer());
writeFileSync(metadata.filename, buffer);
```

### Python

```python Python
import os
import requests

API_KEY = os.environ["AGENTMQ_TOKEN"]
download_id = "download-uuid"

# Get metadata
metadata_response = requests.get(
    f"https://api.agent.mq/v1/browsers/downloads/{download_id}",
    headers={
        "Authorization": API_KEY,
        "Accept": "application/json"
    }
)
metadata = metadata_response.json()
print(f"File: {metadata['filename']} ({metadata['size']} bytes)")

# Download file content
file_response = requests.get(
    f"https://api.agent.mq/v1/browsers/downloads/{download_id}",
    headers={
        "Authorization": API_KEY,
        "Accept": "application/octet-stream"
    }
)
with open(metadata["filename"], "wb") as f:
    f.write(file_response.content)
```

### Delete a download

Remove a download from storage. Returns `204 No Content` on success.

### Node.js

```typescript Node
const API_KEY = process.env.AGENTMQ_TOKEN!;
const downloadId = "download-uuid";

const response = await fetch(
  `https://api.agent.mq/v1/browsers/downloads/${downloadId}`,
  {
    method: "DELETE",
    headers: { "Authorization": API_KEY },
  }
);

if (response.status === 204) {
  console.log("Download deleted");
}
```

### Python

```python Python
import os
import requests

API_KEY = os.environ["AGENTMQ_TOKEN"]
download_id = "download-uuid"

response = requests.delete(
    f"https://api.agent.mq/v1/browsers/downloads/{download_id}",
    headers={"Authorization": API_KEY}
)

if response.status_code == 204:
    print("Download deleted")
```

### Download object

Each download contains the following fields:

| Field       | Type   | Description                                        |
| ----------- | ------ | -------------------------------------------------- |
| `id`        | string | Unique identifier for the download.                |
| `sessionId` | string | The session ID this download belongs to.           |
| `filename`  | string | The filename of the downloaded file.               |
| `mimeType`  | string | The MIME type of the file.                         |
| `size`      | number | File size in bytes.                                |
| `checksum`  | string | SHA256 checksum of the file.                       |
| `createdAt` | string | Timestamp when the file was downloaded (ISO 8601). |

- **List downloads** — [List and filter downloads](/browsers/api)

- **Get download** — [Get metadata or file content](/browsers/api)

- **Delete download** — [Remove a download](/browsers/api)

Source: https://help.agent.mq/browsers/files/downloads/index.mdx
