# Bulk File Downloader: Download Files & Images from URLs (`phaux/bulk-file-downloader`) Actor

Download files (images, PDFs, CSVs, archives) from a list of direct URLs into Apify storage. Use for stored copies or stable links. Input: urls, optional maxFileSizeMb, allowedContentTypes. Output per file: download URL, content type, size, HTTP status, SHA-256. No crawling; video sites refused.

- **URL**: https://apify.com/phaux/bulk-file-downloader.md
- **Developed by:** [Phaux](https://apify.com/phaux) (community)
- **Categories:** Automation, Developer tools
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 file downloadeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Bulk File & Image Downloader: URLs to Storage

Paste a list of file URLs and get **every file saved to Apify storage** with a **direct download link, content type, size, HTTP status and SHA-256 hash**. Images, PDFs, CSV and Excel files, ZIP archives, audio: any direct file link works. It downloads only the URLs you give and never crawls.

### Quick start for AI agents and API users

- **What it does:** Download files (images, PDFs, CSVs, archives) from a list of direct URLs into Apify storage.
- **When to use it:** For stored copies or stable links.
- **Example input** (the form's prefill; it finishes in under a minute):

```json
{
  "urls": [
    "https://www.python.org/static/img/python-logo.png",
    "https://raw.githubusercontent.com/datasets/country-list/main/data.csv"
  ]
}
```

- **Example output:** one real dataset row from run `L1PzvgZwrka7QhpOU` (2026-10-23), with long fields shortened and empty fields left out. Fields not shown are listed in the output table below.

```json
{
  "url": "https://www.python.org/static/img/python-logo.png",
  "finalUrl": "https://www.python.org/static/img/python-logo.png",
  "status": 200,
  "key": "00000-python-logo-9c121e61.png",
  "fileUrl": "https://api.apify.com/v2/key-value-stores/fanC1OY4771KdYuO3/records/00000-python-logo-9c121e61.png?signature=1y9yZo07DY4SXI0yaDz8K",
  "fileName": "python-logo.png",
  "contentType": "image/png",
  "sizeBytes": 15770,
  "sha256": "9c121e619bfe02eaba582d7080eea46fd53ec0b50717e6794a948fada4ae8f3c",
  "redirectCount": 0,
  "downloadMs": 10,
  "downloadedAt": "2026-10-23T19:10:24Z"
}
```

- **Cost (pay per event, Free-plan prices):** File downloaded $0.002; 10 MB transferred $0.001. Failed items are not charged.
- **From an AI agent:** with the Apify MCP server, call `call-actor` with actor `phaux/bulk-file-downloader`, or load it as its own tool with `https://mcp.apify.com?tools=phaux/bulk-file-downloader`. Read results with `get-dataset-items`.

### What does Bulk File & Image Downloader do?

- **Downloads each URL once** (duplicates removed), following up to 5 redirects, and stores the file in the run's key-value store under a readable key (`00007-product-photo-3f9a1c2b.jpg`).
- **Returns one dataset item per stored file**: `fileUrl` (direct link), `fileName`, `contentType`, `sizeBytes`, `status`, `sha256`, `finalUrl` and `redirectCount`.
- **Size caps:** files over `maxFileSizeMb` (default 25 MB, up to 200 MB) are refused before or during download and not charged.
- **Type filter:** `allowedContentTypes` such as `image/*` or `application/pdf`. Web pages (often login, consent or error pages) are refused unless you allow HTML.
- **Clear failure report:** every URL that wasn't stored is listed in `FAILED_URLS` with a reason code.

### Why use it?

- **Archive product images, documents or data files** from a URL list exported by a scraper, a spreadsheet or a CMS.
- **Verify files** with `sha256` and `sizeBytes`, and spot broken links via `FAILED_URLS`.
- **Pay only for stored files**; failed, refused and filtered URLs are free.
- **Polite and safe:** one request at a time per host, robots.txt honoured, private addresses blocked.

Use cases: image datasets for e-commerce or ML, backing up attachments and PDFs, collecting public reports, and turning URL columns from other Actors' results into stored files.

### How to use it

1. Paste direct file URLs into **File URLs**, one per line (up to 100,000). Empty input downloads two small example files.
2. Optionally set **Max file size**, **Allowed content types** and **Max files**.
3. Run it. Open the **Downloaded files** view for direct links, or the **Image preview** view for thumbnails.
4. Download individual files from `fileUrl`, or the whole key-value store from the run's Storage tab.

### Input

```json
{
  "urls": ["https://www.python.org/static/img/python-logo.png", "https://raw.githubusercontent.com/datasets/country-list/main/data.csv"],
  "maxFileSizeMb": 25,
  "allowedContentTypes": ["image/*", "text/csv", "text/plain"],
  "maxItems": 0,
  "requestIntervalSecs": 0.5
}
```

| Field | Type | Default | Description |
|---|---|---|---|
| `urls` | array | — | Direct links to files (images, PDFs, CSV, ZIP...), one per line, up to 100,000. Duplicates are removed. Empty = a small example run. |
| `maxFileSizeMb` | integer | `25` | Larger files are refused before or during download (code too_large) and not charged. 1–200 MB. Files are held in memory while stored, so big limits download fewer files in parallel. |
| `allowedContentTypes` | array | — | Only store files of these types, e.g. image/\*, application/pdf, text/csv. Empty = any type except web pages. |
| `allowHtml` | boolean | `false` | Off (default): a URL that returns a web page instead of a file (often a login, consent or error page) is refused and not charged. |
| `keyPrefix` | string | — | Optional prefix for the key-value store keys (letters, digits and !-\_.'() only). |
| `maxItems` | integer | `0` | Stop after this many files are stored (0 = all). Also caps the cost of a run. |
| `requestIntervalSecs` | number | `0.5` | Politeness delay per host (0.2–30 s). A robots.txt Crawl-delay overrides it when longer. |
| `maxConcurrency` | integer | `8` | Downloads in flight across different hosts (1–32). One host is always served one request at a time. |
| `timeBudgetSecs` | integer | `0` | Stop starting new downloads after this many seconds and keep what is stored (0 = only the run timeout guard). |

### Output example

```json
{
  "url": "https://www.python.org/static/img/python-logo.png",
  "finalUrl": "https://www.python.org/static/img/python-logo.png",
  "status": 200,
  "key": "00000-python-logo-9c121e61.png",
  "fileUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/00000-python-logo-9c121e61.png?signature=…",
  "fileName": "python-logo.png",
  "contentType": "image/png",
  "sizeBytes": 15770,
  "sha256": "9c121e61…",
  "redirectCount": 0,
  "downloadMs": 85,
  "downloadedAt": "2026-10-20T21:31:45Z"
}
```

### Output fields

| Field | Type | Description |
|---|---|---|
| `url` | string | URL from your input. |
| `finalUrl` | string | URL after redirects. |
| `status` | integer | HTTP status of the download (200). |
| `key` | string | Key of the stored file in the run's default key-value store. |
| `fileUrl` | string | Direct link to the stored file in Apify storage. |
| `fileName` | string | File name from Content-Disposition or the URL path. |
| `contentType` | string | Content-Type returned by the server. |
| `sizeBytes` | integer | Size of the stored file in bytes. |
| `sha256` | string | SHA-256 hash of the file (hex), for integrity checks and de-duplication. |
| `redirectCount` | integer | Number of redirects followed (max 5). |
| `downloadMs` | integer | Download time in milliseconds. |
| `downloadedAt` | string | UTC time of the download. |

`FAILED_URLS` reason codes: `invalid_url`, `platform_host`, `blocked_host`, `robots_disallowed`, `http_error`, `too_large`, `html_response`, `content_type_not_allowed`, `empty_file`, `rate_limited`, `host_stopped`, `dns_error`, `network_error`, `timeout`, `too_many_redirects`, `time_budget`, `not_attempted`. `RUN_STATS` has the totals.

### How much does it cost?

Pay per event, the same on every Apify plan. Apify platform usage is included.

| Event | Price |
|---|---|
| File downloaded (per stored file) | $0.002 |
| 10 MB transferred (per started 10 MiB of a stored file, minimum one per file) | $0.001 |

Examples: a 300 KB image costs $0.003; a 25 MB file costs $0.005 ($0.002 + 3 × $0.001); 1,000 small images cost $3.00. Failed, refused and filtered URLs are never charged. Use **Max files** to cap a run.

### Use with AI agents, MCP and the API

- **MCP:** add `https://mcp.apify.com?tools=phaux/bulk-file-downloader` to an MCP client and ask, for example, "download these 20 image URLs and give me links".
- **API:** `POST https://api.apify.com/v2/acts/phaux~bulk-file-downloader/run-sync-get-dataset-items` with `{"urls": ["https://www.python.org/static/img/python-logo.png"]}` returns the file list directly.
- Every input and output field is described in the input and dataset schemas.

### Data source and compliance

- Downloads **only the URLs you provide**. It never crawls, never follows links inside files or pages, and never logs in.
- **You are responsible for having the right to download and store the files** (copyright and licence terms of the source).
- **Video and social platforms are refused** (YouTube, TikTok, Instagram, Facebook, X/Twitter, Vimeo and similar), because their terms don't allow downloading their media with third-party tools.
- **robots.txt is honoured** (RFC 9309) for the user agent `PhauxFileDownloader`; disallowed files are listed as `robots_disallowed`.
- Polite pacing: one request at a time per host, at least `requestIntervalSecs` apart (default 0.5 s). After an **HTTP 429** a host is not contacted again in that run. No proxies, no IP rotation, and no attempts to get around bot protection.
- Only public `http(s)` hosts are fetched; private and internal addresses are refused on every redirect hop.

### Limits

- Up to 100,000 URLs and 200 MB per file per run. Whole files are held in memory while being stored, so large size limits download fewer files in parallel; raise the run memory (up to 4 GB) for big files.
- Files are kept for the data retention period of your Apify plan; download what you need to keep.
- Pages that need JavaScript or a login to reveal the file can't be downloaded; use direct file links.

### FAQ

- **"html_response"?** The URL returned a web page instead of a file, often a login, cookie-consent or error page. Use the direct file link, or switch on **Store HTML pages too** if you really want the page.
- **How do I download everything at once?** Open the run's key-value store in Console and export it, or loop over `fileUrl` in the dataset.
- **Can I keep the original file names?** `fileName` holds the original name; the key adds a sequence number and a short hash so names never collide.
- **Something isn't working?** Open an issue on the Issues tab with the run ID.

# Actor input Schema

## `urls` (type: `array`):

Direct links to files (images, PDFs, CSV, ZIP...), one per line, up to 100,000. Duplicates are removed. Empty = a small example run.

## `maxFileSizeMb` (type: `integer`):

Larger files are refused before or during download (code too_large) and not charged. 1–200 MB. Files are held in memory while stored, so big limits download fewer files in parallel. Default: 25.

## `allowedContentTypes` (type: `array`):

Only store files of these types, e.g. image/\*, application/pdf, text/csv. Empty = any type except web pages.

## `allowHtml` (type: `boolean`):

Off (default): a URL that returns a web page instead of a file (often a login, consent or error page) is refused and not charged.

## `keyPrefix` (type: `string`):

Optional prefix for the key-value store keys (letters, digits and !-\_.'() only). Example: "invoices-2026-".

## `maxItems` (type: `integer`):

Stop after this many files are stored (0 = all). Also caps the cost of a run. Default: 0.

## `requestIntervalSecs` (type: `number`):

Politeness delay per host (0.2–30 s). A robots.txt Crawl-delay overrides it when longer. Default: 0.5.

## `maxConcurrency` (type: `integer`):

Downloads in flight across different hosts (1–32). One host is always served one request at a time. Default: 8.

## `timeBudgetSecs` (type: `integer`):

Stop starting new downloads after this many seconds and keep what is stored (0 = only the run timeout guard). Default: 0.

## Actor input object example

```json
{
  "urls": [
    "https://www.python.org/static/img/python-logo.png",
    "https://raw.githubusercontent.com/datasets/country-list/main/data.csv"
  ],
  "maxFileSizeMb": 25,
  "allowHtml": false,
  "maxItems": 0,
  "requestIntervalSecs": 0.5,
  "maxConcurrency": 8,
  "timeBudgetSecs": 0
}
```

# Actor output Schema

## `files` (type: `string`):

Dataset of stored files with links, type, size, status and hash.

## `storage` (type: `string`):

List of keys in the default key-value store.

## `failed` (type: `string`):

FAILED_URLS: each URL that was not stored, with the reason code.

## `runStats` (type: `string`):

RUN_STATS record.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://www.python.org/static/img/python-logo.png",
        "https://raw.githubusercontent.com/datasets/country-list/main/data.csv"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("phaux/bulk-file-downloader").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "urls": [
        "https://www.python.org/static/img/python-logo.png",
        "https://raw.githubusercontent.com/datasets/country-list/main/data.csv",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("phaux/bulk-file-downloader").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://www.python.org/static/img/python-logo.png",
    "https://raw.githubusercontent.com/datasets/country-list/main/data.csv"
  ]
}' |
apify call phaux/bulk-file-downloader --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,phaux/bulk-file-downloader"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/pnrRaVqOIKAXtnhMo/builds/Yuvtaxk6nGmLOgmWy/openapi.json
