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:
- Create a browser session and get the session ID
- Connect to the session using your preferred framework
- Configure your library’s downloads location
- Perform the download action in your automation script
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));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));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));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.")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.")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.
Retrieving downloaded files
After triggering downloads, retrieve them using the Downloads 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.
List downloads
List all downloads for a session with optional filtering by filename, MIME type, file size, and creation time.
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);
});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:
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`);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).
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);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.
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");
}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). |