# Bulk File Downloader - URL List or Dataset to Files & ZIP (`gazidev/bulk-file-downloader`) Actor

Download files in bulk from a URL list, CSV/JSON file, Google Sheet or Apify dataset field. Saves PDFs, images, audio and video with public links, SHA-256 and MIME type, or packs them into ZIP files. Filters, dedupe, retries. Pay only per file.

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

## Pricing

from $1.20 / 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 Downloader – URL List or Dataset to Files & ZIP (PDF, Images, Video, Audio)

**Download files in bulk from a list of URLs, a CSV/TXT/JSON file, a Google Sheet or another Actor's dataset.** Each file is saved with a **public download link** and comes with its size, MIME type, SHA-256 hash and final URL. You can also get **one ZIP of everything**, split into parts if it's large. It handles any file type: PDF, JPG/PNG/WebP, MP4, MP3, ZIP, DOCX/XLSX, CSV, JSON and more.

- **4 ways to give URLs:** paste a list, upload a **CSV / TSV / TXT / JSON / JSONL** file, link a **Google Sheet**, or **chain another Actor** by dataset ID with field paths like `images[*]`, `pdfUrl` or `files[*].url`
- **Public link for every file** (`storedUrl`), plus an optional **ZIP64 archive** (no 4 GB limit) that you can **split into parts** of N MB
- **Smart file names:** `{index}_{name}`, `{field:title}`, `{field:sku}/{name}` (folders inside the ZIP), `{sha256_8}`, `{host}`, `{date}`
- **Filters:** allowed extensions, content types (`image/`, `application/pdf`…), min/max file size. Oversized downloads are aborted early
- **SHA-256 deduplication:** the same file under different URLs is saved once, and **duplicates are free**
- **Robust:** retries with exponential backoff (honours `Retry-After`), timeouts, a **per-host concurrency limit**, and redirect tracking. It detects "HTML page instead of a file" (login walls, expired links)
- **You only pay for files that were saved.** Errors, blocked URLs, skipped files and duplicates are free rows
- **Cheap:** **$1.20 per 1,000 files** + $0.0015 per 10 MB, HTTP-only, with no browser and no proxy

### What can I use it for?

- **Scraper output to files:** download every product image, PDF datasheet or attachment that another Actor found (e-commerce, real estate, tenders, job ads, court records)
- **Document collection:** pull hundreds of PDFs, reports, invoices or filings from a spreadsheet of links
- **Media archiving and backups:** move your own assets from a CDN, bucket or old CMS (with custom headers for private endpoints)
- **ML / AI datasets:** collect images, audio or documents with clean names, hashes and no duplicates, ready for training or RAG ingestion
- **Data migration:** turn a Google Sheet of URLs into one ZIP organised in folders by SKU, category or customer
- **Link checking with proof:** keep the actual file, its SHA-256 and the final URL after redirects

### How to use it

1. Paste file URLs into **File URLs** (the prefill downloads 4 public-domain images from Wikimedia Commons and a small W3C sample PDF), or use **URL list file**, **Google Sheet link** or **Apify dataset ID**.
2. Optional: set **Field paths / column names**, filters, the **File name template** and **Create ZIP archive**.
3. Click **Start**. Each file appears in the **Output** tab with a download link. ZIPs and the run summary are in the key-value store (`files.zip`, `OUTPUT`).

### Input

| Field | Description |
|---|---|
| `urls` | Direct file URLs (one per line) |
| `bulkText` | Any pasted text; every http(s) URL in it is used |
| `urlListFile` | Uploaded file or link to one: CSV/TSV (columns from `fieldPaths`, or all cells), JSON array (strings or objects), JSONL, TXT |
| `googleSheetUrl` | Google Sheets share link (anyone with the link can view) or a "Publish to web" CSV link; `#gid=` selects the tab |
| `datasetId` + `fieldPaths` | Chain with another Actor. Paths: `pdfUrl`, `images[*]`, `files[*].url`, `media.0.src`, `a.b[2]`. Objects with `url`/`src`/`href` are understood. Empty = every URL in the row |
| `maxFiles` | Stop after N saved files (0 = no limit) |
| `allowedExtensions`, `allowedContentTypes`, `minFileSizeKb`, `maxFileSizeMb` | Filters (detected from file content, headers and URL) |
| `skipHtmlPages`, `dedupe` | Skip "web page instead of file" responses; SHA-256 dedupe (both on by default) |
| `fileNameTemplate` | `{index}`, `{n}`, `{name}`, `{host}`, `{sha256}`, `{sha256_8}`, `{date}`, `{field:<column or path>}`; `/` makes folders in the ZIP |
| `saveToKeyValueStore` | One public link per file (default on). Turn off for ZIP only |
| `createZip`, `zipPartSizeMb`, `zipName`, `zipCompress` | ZIP64 archive, optional split into `files-001.zip`, `files-002.zip`…, optional deflate |
| `keyValueStoreName` | Save into a named store that is kept permanently |
| `includeSkipped` | List filtered files and duplicates in the dataset (free) |
| `maxConcurrency`, `maxConcurrencyPerHost`, `requestTimeoutSecs`, `maxRetries`, `userAgent`, `headers` | Advanced |

Example: download every product image and manual from a scraper run into one ZIP with a folder per SKU:

```json
{
  "datasetId": "aBcD1234EfGh5678",
  "fieldPaths": "images[*], manual.pdfUrl",
  "fileNameTemplate": "{field:sku}/{field:title}_{n}",
  "createZip": true,
  "saveToKeyValueStore": false
}
```

Example: a CSV upload where the URLs are in the column `file` and the names come from `title`:

```json
{
  "urlListFile": "https://example.com/my-links.csv",
  "fieldPaths": "file",
  "fileNameTemplate": "{field:title}",
  "allowedExtensions": ["pdf", "jpg"]
}
```

### Output

One dataset row per URL. Export it as JSON, CSV, Excel or HTML, or read it through the API. The files are in the run's key-value store.

```json
{
  "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
  "status": "downloaded",
  "error": null,
  "fileName": "00005_dummy.pdf",
  "storedUrl": "https://api.apify.com/v2/key-value-stores/aBcD1234EfGh5678/records/00005_dummy.pdf",
  "key": "00005_dummy.pdf",
  "zipFile": "files.zip",
  "zipUrl": "https://api.apify.com/v2/key-value-stores/aBcD1234EfGh5678/records/files.zip",
  "zipPath": "00005_dummy.pdf",
  "index": 5,
  "source": "urls",
  "sourceRow": null,
  "finalUrl": null,
  "httpStatus": 200,
  "contentType": "application/pdf",
  "extension": "pdf",
  "bytes": 13264,
  "sha256": "3df79d34abbca99308e79cb94461c1893582604d68329a41fd4bec1885e6adb4",
  "durationMs": 65,
  "downloadedAt": "2026-10-16T19:46:03+00:00"
}
```

`status` is one of:

- `downloaded`: the file was saved (charged)
- `failed`: HTTP 404/403/5xx, timeout, DNS error or empty response
- `blocked`: a social or video platform, or a private network address
- `skipped-size`, `skipped-type`, `skipped-extension` or `skipped-html`: removed by a filter
- `duplicate`: the same SHA-256 as an earlier file, with `duplicateOf` pointing to it

Only `downloaded` rows are charged. The `OUTPUT` record has the run summary: counts, bytes and the ZIP links.

### Pricing

| Event | Price |
|---|---|
| File downloaded (primary) | **$0.0012** ($1.20 per 1,000 files) |
| Every 10 MB of downloaded data | $0.0015 |
| ZIP archive created (per ZIP file) | $0.003 |
| Actor start | $0.00005 |

| Actor | 1,000 small files |
|---|---|
| **This Actor** | **$1.20** |
| rationalistic_candle_ucn bulk file downloader | $2.00 (+ $0.001 / 10 MB) |
| automa-flow file downloader | $4.00 |
| nerolabs file downloader | $4.00 (+ $0.003 / 10 MB, $0.01 ZIP) |

*Competitor prices are from the Apify Store on 16 October 2026 and may change.* Failed, blocked, skipped and duplicate URLs are free. Set **Maximum cost per run** in the run options, and the Actor stops gracefully when it is reached (finished files and the ZIP are kept).

### FAQ

**Who is responsible for the files I download?** **You are.** Only download files you have the right to download and use: your own files, public-domain or openly licensed content, data you are licensed to access, or other lawful uses. The Actor does not grant any rights to the files, and you must respect the source websites' terms of service and copyright.

**Why are YouTube, TikTok, Instagram, Facebook, X/Twitter, Vimeo and similar URLs blocked?** These platforms forbid downloading their content in their terms of service, so their pages and media CDNs (including redirects to them) are refused with a free `blocked` row. Use direct file links from sources you are allowed to download from.

**Does it work with links that need a login?** Only through the `headers` option for endpoints you control (e.g. `Authorization: Bearer …` for your own server or storage bucket). Login walls usually return an HTML page, which is reported as `skipped-html`.

**What about Google Drive / Dropbox links?** Use direct download links (Dropbox `?dl=1`, Google Drive `uc?export=download&id=…` for small public files). Preview pages are HTML and are skipped.

**How big can files be?** The default limit is 500 MB per file and can be raised. Files are streamed to disk, and ZIPs use ZIP64, so archives larger than 4 GB work. For very large jobs, split the ZIP (`zipPartSizeMb`) and give the run more memory and time.

**How long are files kept?** Default run storage follows your plan's data retention. Set `keyValueStoreName` to keep files in a named store permanently.

**Can I feed it URLs from another Actor?** Yes. Set `datasetId` to that run's dataset ID and `fieldPaths` to the field(s) with URLs, e.g. `images[*]` or `documents[*].url`. Use `{field:title}` in the file name to keep names meaningful.

### Use with AI agents / Apify MCP

- **Apify MCP server:** add `https://mcp.apify.com/?actors=gazidev/bulk-file-downloader` to Claude Desktop, Cursor, VS Code or any MCP client. An agent can then ask: "Download these 40 PDF links, zip them and give me the link."
- **API:** `POST https://api.apify.com/v2/acts/gazidev~bulk-file-downloader/run-sync-get-dataset-items?token=...` with `{"urls": ["https://example.com/a.pdf"]}` returns rows with `storedUrl` download links synchronously for small jobs.
- **Tip for agents:** filter rows on `status == "downloaded"` and use `storedUrl` or `zipUrl`. `sha256`, `bytes` and `contentType` let you verify or route files without downloading them again.

# Actor input Schema

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

Direct links to files of any type: PDF, images, audio, video, ZIP, Office documents, CSV... One URL per line. Social media and video platforms (YouTube, TikTok, Instagram, Facebook, X, Vimeo...) are blocked.

## `bulkText` (type: `string`):

Paste a column from a spreadsheet, a list, HTML or any text: every http(s) URL in it is downloaded.

## `urlListFile` (type: `string`):

Upload a file or paste a link to one. CSV/TSV: URLs are taken from the columns named in `fieldPaths` (or from every cell). JSON: an array of URLs, or an array of objects read with `fieldPaths`. TXT: one URL per line.

## `googleSheetUrl` (type: `string`):

Normal share link (the sheet must be shared as 'Anyone with the link can view') or a 'Publish to web' CSV link. Add `#gid=...` to pick a tab. Columns are selected with `fieldPaths`.

## `datasetId` (type: `string`):

Download the files referenced in another run's dataset, e.g. a product, real-estate or document scraper. Use `fieldPaths` to say where the URLs are. Several IDs can be comma-separated.

## `fieldPaths` (type: `string`):

Comma-separated. For datasets and JSON: `pdfUrl`, `images[*]`, `files[*].url`, `media.0.src`. For CSV / Google Sheets: column names. Empty = every URL found in the row.

## `maxFiles` (type: `integer`):

Stop after this many files are saved (0 = no limit). Failed, filtered and duplicate URLs don't count.

## `allowedExtensions` (type: `array`):

Keep only these file types, e.g. `pdf`, `jpg`, `png`, `mp4`. The extension is detected from the file content, headers and URL. Empty = all.

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

Keep only these MIME types or prefixes, e.g. `image/`, `application/pdf`, `video/`. Empty = all.

## `minFileSizeKb` (type: `integer`):

Skip smaller files (icons, placeholders). 0 = no minimum.

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

Larger files are skipped (download aborted as soon as the limit is passed, not charged).

## `skipHtmlPages` (type: `boolean`):

If a 'file' link returns a web page instead (login wall, landing page, expired link), skip it for free instead of saving the HTML.

## `dedupe` (type: `boolean`):

Byte-identical files under different URLs are saved once. Duplicates are never charged.

## `fileNameTemplate` (type: `string`):

Placeholders: {index} (input position, 00001), {n} (saved count), {name} (original file name), {host}, {sha256}, {sha256_8}, {date}, {field:<column or path>} (value from the CSV/JSON/dataset row, e.g. {field:title}). Use `/` to create folders inside the ZIP. The extension is added automatically.

## `saveToKeyValueStore` (type: `boolean`):

Store every file in the key-value store and return a direct download link (`storedUrl`). Turn off to get only the ZIP.

## `createZip` (type: `boolean`):

Bundle all files into `files.zip` (ZIP64, no 4 GB limit). Charged once per ZIP file.

## `zipPartSizeMb` (type: `integer`):

0 = one ZIP. Otherwise ZIPs are split into parts `files-001.zip`, `files-002.zip`... of about this size (handy for email or slow connections).

## `zipName` (type: `string`):

Base name of the ZIP file(s).

## `zipCompress` (type: `boolean`):

Off = store files as-is (fast; best for images, video, PDF, which are already compressed). On = smaller ZIP for text, CSV, JSON, XML.

## `keyValueStoreName` (type: `string`):

Save files and ZIPs into this named store, which is kept forever (default run storage expires with your plan's retention).

## `includeSkipped` (type: `boolean`):

Free rows for filtered and duplicate files. Failed and blocked URLs are always listed (free).

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

Total number of downloads running at the same time.

## `maxConcurrencyPerHost` (type: `integer`):

Politeness limit per website, so a single server isn't overloaded.

## `requestTimeoutSecs` (type: `integer`):

Connection / read inactivity timeout per request.

## `maxRetries` (type: `integer`):

Retries for timeouts, connection errors, 429 and 5xx responses, with exponential backoff (Retry-After respected).

## `userAgent` (type: `string`):

Custom User-Agent. Empty = a default that identifies this Actor (some sites, like Wikimedia, require that).

## `headers` (type: `object`):

Optional headers sent with every request, e.g. {"Authorization": "Bearer ..."} for your own server or bucket.

## Actor input object example

```json
{
  "urls": [
    "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg/500px-Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a5/Tsunami_by_hokusai_19th_century.jpg/500px-Tsunami_by_hokusai_19th_century.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg/330px-Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/c/c4/PM5544_with_non-PAL_signals.png",
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ],
  "maxFiles": 0,
  "minFileSizeKb": 0,
  "maxFileSizeMb": 500,
  "skipHtmlPages": true,
  "dedupe": true,
  "fileNameTemplate": "{index}_{name}",
  "saveToKeyValueStore": true,
  "createZip": false,
  "zipPartSizeMb": 0,
  "zipName": "files",
  "zipCompress": false,
  "includeSkipped": true,
  "maxConcurrency": 10,
  "maxConcurrencyPerHost": 4,
  "requestTimeoutSecs": 60,
  "maxRetries": 3
}
```

# Actor output Schema

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

No description

## `results` (type: `string`):

No description

## `zip` (type: `string`):

No description

## `store` (type: `string`):

No description

## `summary` (type: `string`):

No description

# 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://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg/500px-Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg",
        "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a5/Tsunami_by_hokusai_19th_century.jpg/500px-Tsunami_by_hokusai_19th_century.jpg",
        "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg/330px-Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg",
        "https://upload.wikimedia.org/wikipedia/commons/c/c4/PM5544_with_non-PAL_signals.png",
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("gazidev/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://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg/500px-Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg",
        "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a5/Tsunami_by_hokusai_19th_century.jpg/500px-Tsunami_by_hokusai_19th_century.jpg",
        "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg/330px-Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg",
        "https://upload.wikimedia.org/wikipedia/commons/c/c4/PM5544_with_non-PAL_signals.png",
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("gazidev/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://upload.wikimedia.org/wikipedia/commons/thumb/e/ea/Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg/500px-Van_Gogh_-_Starry_Night_-_Google_Art_Project.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a5/Tsunami_by_hokusai_19th_century.jpg/500px-Tsunami_by_hokusai_19th_century.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg/330px-Mona_Lisa%2C_by_Leonardo_da_Vinci%2C_from_C2RMF_retouched.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/c/c4/PM5544_with_non-PAL_signals.png",
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ]
}' |
apify call gazidev/bulk-file-downloader --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,gazidev/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/FeWA4Kt00cRrmX80a/builds/5cPZuwspTR7Wo61Id/openapi.json
