Skip to main content

Download API

Run custom Puppeteer code and return files that Chrome downloads during execution. The response includes appropriate Content-Type and Content-Disposition headers. You can load external libraries via import and use ESM modules that run inside the browser context.

Endpoint

  • Method: POST
  • Path: /download
  • Auth: token query parameter (?token=)
  • Content-Type: application/javascript or application/json
  • Response: */* (based on downloaded file)

See the OpenAPI reference for complete details.

Quickstart

The /download API accepts requests in two content types: application/javascript (raw code) and application/json (code + context data).

Sending code as Javascript

This example navigates to a page with CSV sample files, clicks the first .csv download link, and waits for the file to download.

JS Code

export default async ({ page }) => {
await page.goto("https://filesamples.com/formats/csv", {
waitUntil: "networkidle2",
});
await page.click('a[href*=".csv"]');
await new Promise((r) => setTimeout(r, 5000));
};
curl -s -X POST "https://production-sfo.browserless.io/download?token=YOUR_API_TOKEN_HERE" -H "Content-Type: application/javascript" -d 'export default async({page})=>{await page.goto("https://filesamples.com/formats/csv",{waitUntil:"networkidle2"});await page.click("a[href*=\".csv\"]");await new Promise(r=>setTimeout(r,5000));}' -o sample.csv

Sending code as JSON

You can send a JSON payload with the following fields:

  • code: String, required — your JavaScript code as a string
  • context: Object, optional — variables to pass to your function

This example uses context to pass the target URL, navigates to the page, and clicks the first .csv download link.

JS Code

export default async ({ page, context }) => {
await page.goto(context.url, { waitUntil: "networkidle2" });
await page.click('a[href*=".csv"]');
await new Promise((r) => setTimeout(r, 5000));
};
curl -s -X POST "https://production-sfo.browserless.io/download?token=YOUR_API_TOKEN_HERE" -H "Content-Type: application/json" -d '{"code":"export default async({page,context})=>{await page.goto(context.url,{waitUntil:\"networkidle2\"});await page.click(\"a[href*=\\\".csv\\\"]\");await new Promise(r=>setTimeout(r,5000));}","context":{"url":"https://filesamples.com/formats/csv"}}' -o sample.csv

Importing libraries

Since the /download API uses ESM modules, you can use import syntax over HTTP to load modules. For instance, let's try loading the Faker module.

JS Code

import { faker } from "https://esm.sh/@faker-js/faker";

export default async function ({ page }) {
const rndName = faker.person.fullName();
const rndEmail = faker.internet.email();

await page.evaluate(
(name, email) => {
const jsonStr = JSON.stringify({ name, email });
const jsonContent = `data:application/json,${jsonStr}`;
const encodedUri = encodeURI(jsonContent);
const link = document.createElement("a");

link.setAttribute("href", encodedUri);
link.setAttribute("download", "data.json");
document.body.appendChild(link);
return link.click();
},
rndName,
rndEmail
);
}

cURL

curl -X POST "https://production-sfo.browserless.io/download?token=YOUR_API_TOKEN_HERE" -H 'Content-Type: application/javascript' -d 'import{faker}from"https://esm.sh/@faker-js/faker";export default async function({page}){const n=faker.person.fullName();const e=faker.internet.email();await page.evaluate((name,email)=>{const j=JSON.stringify({name,email});const c=`data:application/json,${j}`;const u=encodeURI(c);const l=document.createElement("a");l.setAttribute("href",u);l.setAttribute("download","data.json");document.body.appendChild(l);return l.click();},n,e);}'

Response

{
"name": "Jasmine Littel",
"email": "Giovanna26@hotmail.com"
}

Generate and download a file

If there isn't a file on the site that you need to download, you can create the content you want to download and then trigger a download by creating a link element on the page.

This example navigates to Hacker News, scrapes the top 10 story titles and URLs, creates a JSON blob, and triggers a download.

JS Code

export default async ({ page }) => {
await page.goto("https://news.ycombinator.com/", { waitUntil: "networkidle2" });
const stories = await page.$$eval(".athing", (rows) =>
rows.slice(0, 10).map((r) => ({
title: r.querySelector(".titleline > a")?.textContent || "",
url: r.querySelector(".titleline > a")?.href || "",
}))
);
await page.evaluate((d) => {
const a = document.createElement("a");
a.href = URL.createObjectURL(
new Blob([JSON.stringify(d, null, 2)], { type: "application/json" })
);
a.download = "hn.json";
document.body.appendChild(a);
a.click();
}, stories);
await new Promise((r) => setTimeout(r, 2000));
};

cURL

curl -s -X POST "https://production-sfo.browserless.io/download?token=YOUR_API_TOKEN_HERE" -H "Content-Type: application/javascript" -d 'export default async({page})=>{await page.goto("https://news.ycombinator.com/",{waitUntil:"networkidle2"});const s=await page.$$eval(".athing",r=>r.slice(0,10).map(r=>({title:r.querySelector(".titleline > a")?.textContent||"",url:r.querySelector(".titleline > a")?.href||""})));await page.evaluate(d=>{const a=document.createElement("a");a.href=URL.createObjectURL(new Blob([JSON.stringify(d,null,2)],{type:"application/json"}));a.download="hn.json";document.body.appendChild(a);a.click()},s);await new Promise(r=>setTimeout(r,2000));}' -o hn.json

Response

[
{
"title": "Show HN: A open-source tool for building AI agents",
"url": "https://example.com/article1"
},
{
"title": "Why Rust is the future of systems programming",
"url": "https://example.com/article2"
}
]

You can also send the code as a JSON payload. This example scrapes book data from Books to Scrape, builds a CSV string, and uses context to pass the page number.

JS Code

export default async ({ page, context }) => {
await page.goto(
"https://books.toscrape.com/catalogue/page-" + context.pageNumber + ".html",
{ waitUntil: "networkidle2" }
);
const books = await page.$$eval(".product_pod", (nodes) =>
nodes.map((e) => ({
title: e.querySelector("h3 a").getAttribute("title"),
price: e.querySelector(".price_color").textContent,
}))
);
const csv = ["Title,Price", ...books.map((x) => x.title + "," + x.price)].join(
"\n"
);
await page.evaluate((d) => {
const a = document.createElement("a");
a.href = URL.createObjectURL(new Blob([d], { type: "text/csv" }));
a.download = "books.csv";
document.body.appendChild(a);
a.click();
}, csv);
await new Promise((r) => setTimeout(r, 2000));
};

cURL

curl -s -X POST "https://production-sfo.browserless.io/download?token=YOUR_API_TOKEN_HERE" -H "Content-Type: application/json" -d '{"code":"export default async({page,context})=>{await page.goto(\"https://books.toscrape.com/catalogue/page-\"+context.pageNumber+\".html\",{waitUntil:\"networkidle2\"});const b=await page.$$eval(\".product_pod\",n=>n.map(e=>({title:e.querySelector(\"h3 a\").getAttribute(\"title\"),price:e.querySelector(\".price_color\").textContent})));const csv=[\"Title,Price\",...b.map(x=>x.title+\",\"+x.price)].join(\"\\n\");await page.evaluate(d=>{const a=document.createElement(\"a\");a.href=URL.createObjectURL(new Blob([d],{type:\"text/csv\"}));a.download=\"books.csv\";document.body.appendChild(a);a.click()},csv);await new Promise(r=>setTimeout(r,2000));}","context":{"pageNumber":1}}' -o books.csv

Response

Title,Price
A Light in the Attic,£51.77
Tipping the Velvet,£53.74
Soumission,£50.10