> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://platform.bctrl.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://platform.bctrl.ai/_mcp/server.

# Collect Downloads & Artifacts

> Trigger downloads in the cloud browser and pull everything the run produced as one archive.

Files downloaded in a runtime don't land on your machine - they land on the [run](/sdk/run-files), alongside screenshots and agent outputs. When the run ends, export them as a single archive with a download URL.

```ts
import { Bctrl } from "@bctrl/sdk";
import { chromium } from "playwright";

const bctrl = new Bctrl({ apiKey: process.env.BCTRL_API_KEY! });

const runtime = await bctrl.runtimes.create({ type: "browser", name: "downloads-recipe" });
const { connectUrl, runId } = await bctrl.runtimes.start(runtime.id);

const browser = await chromium.connectOverCDP(connectUrl);
const context = browser.contexts()[0] ?? (await browser.newContext());
const page = context.pages()[0] ?? (await context.newPage());

// Trigger a download like a user would.
await page.goto("https://acme.com/reports");
await page.click("text=Download CSV");
await page.waitForTimeout(5_000); // let the download finish

await browser.close();
await bctrl.runtimes.stop(runtime.id);

// See what the run produced...
for await (const file of bctrl.runs.files.iter(runId)) {
  console.log(file.name, file.sizeBytes, file.contentType);
}

// ...and export the downloads as one archive.
const archive = await bctrl.runs.files.export(runId, {
  name: "acme-reports.zip", // must end in .zip
  type: ["download"],
});

console.log(archive.downloadUrl); // fetch this to get the ZIP
```

Export is idempotent - pass an idempotency key so a retried job doesn't create duplicate archives (see [Idempotency](/sdk/essentials)).

## Feeding files in

The reverse direction works while the runtime is live: `upload` pushes local bytes into the browser's workspace, `stage` copies a durable [file](/sdk/files) into it - useful when the automation needs an input CSV or a document to submit:

```ts
await bctrl.runtimes.files.upload(runtime.id, {
  file: new Blob(["name,email\nada@acme.com"]),
  runtimePath: "/work/input.csv",
});
```

## Next

* [Run Files](/sdk/run-files) - listing, filtering, exporting
* [Runtimes](/sdk/runtimes) - stage and collect on a live runtime
* [Show Customers a Replay](/cookbook/customer-replay) - pair artifacts with the session replay