# Download Files from URLs - Images, PDFs, Videos & ZIP (`neverempty/download-files-from-urls`) Actor

Bulk download files from a list of URLs or a dataset column: images, PDFs, videos, audio and documents, streamed to a key-value store and optionally packed into ZIP files. Each row gives the file URL, name, type, size and SHA-256. Failed URLs come back free with the reason.

- **URL**: https://apify.com/neverempty/download-files-from-urls.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Automation, Developer tools, E-commerce
- **Stats:** 1 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Download Files from URLs - Images, PDFs, Videos & ZIP

Give it a list of file URLs, or a dataset from another Actor plus the name of the field that holds the URLs. It downloads every file and saves it to an Apify key-value store. **Each file gets one row** with a direct link to the saved copy, the file name, type, size in bytes and SHA-256. You can also ask for everything in a ZIP.

- **Any file type.** Images, PDFs, videos, audio, Office documents, archives. The type is read from the file's first bytes, not only from the URL, so a link without an extension still gets the right name and type.
- **Straight from a scraper's results.** Point it at a dataset (for example the output of a product or listing scraper) and the field with the image or file URL. Fields that hold a list of URLs, or objects with `url`, `src` or `href`, are read in full. Nested fields work with dots (`media.0.url`).
- **One file or one ZIP.** `files` saves each file as its own record with its own link. `zip` packs everything into `files.zip` without compression, so it is fast and every file stays byte-for-byte the same. `both` does both. A large ZIP is split into parts (`files-part2.zip`, ...) so it always fits the run's disk.
- **Large files are streamed.** Files are written to disk and uploaded from there, not held in memory. Measured on Apify at 256 MB memory: a 113 MB TIFF and a 134 MB TIFF in one run, saved as files and as a 2-part ZIP.
- **Check the copy.** Every row has the SHA-256 and the size of the saved file, so you can check it against the source or spot duplicate files.
- **Clear file names.** The file name comes from the URL or the server's `Content-Disposition`. Two different files with the same name get `-2`, `-3`, ... Unsafe characters are replaced.
- **Not charged for files it could not save.** 404, a web page instead of a file, too large, wrong type, blocked by robots.txt, needs a sign-in, no connection: each comes back as a free row that says why. A run where no file could be saved is not charged at all, not even the start fee.
- **Only the URLs you give it.** It does not crawl sites or follow links inside pages.

Measured on Apify at 256 MB memory: 100 Wikimedia Commons photos (23 MB) in 43 s; 3 files of 250 MB in total with `both` in 90 s.

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `urls` | array of URLs | (example photo) | Direct links to the files, one per line. If this and the dataset are both left out, one example photo from Wikimedia Commons is downloaded and the log says so. |
| `datasetId` | dataset | none | An Apify dataset whose rows hold file URLs, for example a scraper's results. Its URLs are added to `urls`. |
| `datasetField` | text | `url` | The field of each dataset row that holds the URL (`imageUrl`, `images`, `media.0.url`, ...). |
| `saveAs` | `files` / `zip` / `both` | `files` | Separate records, one ZIP (split into parts when large), or both. |
| `fileTypes` | array | all types | Keep only these kinds: `image`, `video`, `audio`, `pdf`, `document`, `archive`, `other`, or extensions such as `jpg`, `mp4`, `docx`. Other files come back as free `type-not-allowed` rows. |
| `skipWebPages` | boolean | `true` | An address that returns an HTML page instead of a file is not saved (free `web-page` row). Off: the HTML is saved like any other file. |
| `outputKeyValueStoreId` | key-value store | this run's store | Where to save the files. Choose your own store to keep the files and links after your plan's data retention period. |
| `maxFileMegabytes` | 1-4000 | `500` | A file larger than this is not saved (free `too-large` row). |
| `requestTimeoutSecs` | 5-600 | `60` | A download that receives no data for this long is stopped and tried again. A slow but moving download is not stopped. |
| `maxConcurrency` | 1-16 | `4` | Files downloaded at the same time. Lower it if a site answers 429. |

Example:

```json
{
  "datasetId": "<your scraper's dataset id>",
  "datasetField": "imageUrl",
  "saveAs": "both",
  "fileTypes": ["image"]
}
```

### Output

One dataset row per URL. `outputUrl` opens the saved file directly (a signed link, no API token needed). `zipUrl` and `zipEntry` say which ZIP part holds it.

```json
{
  "status": "ok",
  "position": 2,
  "url": "https://upload.wikimedia.org/wikipedia/commons/f/f0/Paris_view_from_Eiffel.jpg",
  "finalUrl": "https://upload.wikimedia.org/wikipedia/commons/f/f0/Paris_view_from_Eiffel.jpg",
  "outputUrl": "https://api.apify.com/v2/key-value-stores/<store>/records/Paris_view_from_Eiffel.jpg?signature=<signature>",
  "key": "Paris_view_from_Eiffel.jpg",
  "keyValueStoreId": "<store>",
  "fileName": "Paris_view_from_Eiffel.jpg",
  "contentType": "image/jpeg",
  "fileType": "image",
  "extension": "jpg",
  "bytes": 2762548,
  "sha256": "4b4a6566a60033ac051e2b5a6cc8a228e165630738e73fe046b3f43c5e6e83e8",
  "zipUrl": "https://api.apify.com/v2/key-value-stores/<store>/records/files.zip?signature=<signature>",
  "zipEntry": "files.zip/Paris_view_from_Eiffel.jpg",
  "httpStatus": 200,
  "redirects": 0,
  "note": null
}
```

All columns: `status`, `position` (order in your input), `url`, `finalUrl` (after redirects), `outputUrl`, `key`, `keyValueStoreId`, `fileName`, `contentType`, `fileType`, `extension`, `bytes`, `sha256`, `zipUrl`, `zipEntry`, `httpStatus`, `redirects`, `durationMs`, `note`, `downloadedAt`.

The same URL given twice is downloaded and charged once (the log says how many were skipped).

#### Free rows (not charged)

| `status` | When |
|---|---|
| `not-found` | The site says the file does not exist (404 or 410). |
| `web-page` | The address returned an HTML page, not a file (with `skipWebPages` on). |
| `type-not-allowed` | The file is not one of the `fileTypes` you chose. |
| `too-large` | Larger than `maxFileMegabytes`, or larger than the run's temporary disk (the row says to run with more memory). |
| `empty-file` | The server sent a file with no content. |
| `robots-disallowed` / `robots-unreachable` | The site's robots.txt does not allow automated reading of that address, or could not be read. |
| `login-required` / `blocked` | The file needs a sign-in, or the site refused the reader or showed a check page. It does not sign in, solve check pages or switch to proxies. |
| `http-error` / `unreachable` / `unreadable` / `download-failed` | Another HTTP error, no connection, no usable answer after retries (429 and 5xx are retried), or a download that broke off. |
| `storage-failed` | The file was downloaded but could not be saved to the key-value store. |
| `bad-input` | Not an http(s) URL, a private or internal network address, or a dataset field that holds no URL (the row says why). |
| `time-limit` / `budget-reached` | The run was close to its timeout or to the maximum total charge you set; the remaining files were not started and not charged. |

### Pricing

Pay per event:

- **File downloaded:** $0.0049 per file on the Free plan, down to $0.0035 on higher plans. Charged only when the file is saved and its row is returned. Includes the first 10 MB of the file.
- **Each further 10 MB:** $0.00295 per 10 MB above the first 10 MB on the Free plan, down to $0.0023. A 25 MB video counts 2 extra blocks; a 4 MB photo counts none. This is counted per stored copy: with **Save as = both** (separate files and the ZIP) each file is stored twice, so a 25 MB video counts 4 extra blocks. Files under 10 MB cost the same in every mode.
- **Run start:** $0.006 per run on the Free plan, down to $0.0044. Charged once, only in a run that saves at least one file.

Platform usage (compute, storage writes and data transfer) is included; you do not pay it on top. Before each file is downloaded, the Actor checks that the maximum total charge you set has room for it; a file that would go over is not downloaded and comes back as a free `budget-reached` row.

Examples on the Free plan: 1,000 product photos under 10 MB each = $4.906. One 100 MB video = $0.006 + $0.0049 + 9 x $0.00295 = $0.038 as files or as a ZIP, or $0.006 + $0.0049 + 18 x $0.00295 = $0.064 with both.

### Memory and large files

The default memory is 256 MB. Memory is not what limits file size, because files are streamed. Disk is: the run's temporary disk grows with its memory (a run at 256 MB memory had 512 MB of disk, and the log prints the free disk at start). Measured at 256 MB: files up to 134 MB each, 250 MB in one run, saved as files and ZIP. For single files larger than about 200 MB, run with more memory. If a file does not fit, it comes back as a free `too-large` row that says so; the run does not crash and the other files are kept.

### Use it from code

```bash
curl -X POST "https://api.apify.com/v2/acts/neverempty~download-files-from-urls/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"urls":["https://example.com/report.pdf","https://example.com/photo.jpg"]}'
```

The response is the dataset rows; `outputUrl` is the saved file.

### Notes

- Files are fetched with a plain HTTP client from Apify's own network, following each site's robots.txt. No proxies are used to get around a refusal.
- Addresses that point to private or internal networks are not requested.
- Only download files you have the right to copy.

### Support

Found a problem or need another option? Open an issue on the Issues tab of this Actor.

# Actor input Schema

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

Direct links to the files to download: images, PDFs, videos, audio, documents, archives or any other file. One URL per line. Only these URLs are downloaded; links inside pages are not followed. If this and the dataset field are both left out, one example photo from Wikimedia Commons is downloaded and the log says so.

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

An Apify dataset, for example the results of a scraper run, whose rows hold file URLs. Use it together with the field name below. URLs from here are added to the URLs above.

## `datasetField` (type: `string`):

The field of each dataset row that holds the file URL, for example url, imageUrl or images. A field that holds a list of URLs, or objects with url, src or href, is read in full. Use dots for nested fields, for example media.0.url. Default: url.

## `saveAs` (type: `string`):

files = each file is saved as its own record in a key-value store with its own URL. zip = all files are packed into files.zip (no compression, so it is fast and files stay byte-for-byte the same). both = separate files and the ZIP; each file is then stored twice, so the size charge for files over 10 MB (each further 10 MB) is counted twice. Files under 10 MB cost the same in every mode.

## `fileTypes` (type: `array`):

Keep only files of these kinds: image, video, audio, pdf, document, archive, other, or file extensions such as jpg, png, mp4 or docx. The type is read from the file itself, not only from the URL. Other files come back as free rows. Empty = keep every type.

## `skipWebPages` (type: `boolean`):

On (default): an address that returns an HTML web page instead of a file is not saved and comes back as a free row. Off: the HTML is saved like any other file.

## `outputKeyValueStoreId` (type: `string`):

By default the files go to this run's own key-value store, which is deleted with the run after your plan's data retention period. Choose a key-value store here to keep the files (and their URLs) for good.

## `maxFileMegabytes` (type: `integer`):

A file larger than this is not saved and comes back as a free too-large row. Files are streamed to storage, so large files do not need more memory; very large files need more disk space, which grows with the run's memory.

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

A download that receives no data for this long is stopped and tried again. A slow but moving download of a large file is not stopped.

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

How many files to download at the same time. Lower it if a site answers 429 (too many requests).

## Actor input object example

```json
{
  "urls": [
    "https://upload.wikimedia.org/wikipedia/commons/f/f0/Paris_view_from_Eiffel.jpg"
  ],
  "saveAs": "files",
  "skipWebPages": true,
  "maxFileMegabytes": 500,
  "requestTimeoutSecs": 60,
  "maxConcurrency": 4
}
```

# Actor output Schema

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

One row per URL: the saved file's URL, key and key-value store, file name, content type, file type, extension, size in bytes, SHA-256, the ZIP file and entry name when ZIP output is chosen, the final URL after redirects and the HTTP status. A URL that could not be downloaded, returned a web page, is larger than the size limit, is blocked by robots.txt or needs a sign-in comes back as a free row that says why.

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

The downloaded files, in this run's key-value store (or the store given in outputKeyValueStoreId).

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

All downloaded files in one ZIP (files.zip) when saveAs is zip or both, in the same key-value store as the files. A very large run is split into files.zip, files-part2.zip and so on; the OUTPUT record lists every part.

# 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/f/f0/Paris_view_from_Eiffel.jpg"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/download-files-from-urls").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/f/f0/Paris_view_from_Eiffel.jpg"] }

# Run the Actor and wait for it to finish
run = client.actor("neverempty/download-files-from-urls").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/f/f0/Paris_view_from_Eiffel.jpg"
  ]
}' |
apify call neverempty/download-files-from-urls --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/download-files-from-urls"
        }
    }
}
```

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/uDAIDLefGfyDY5X9y/builds/XhfJIajEL6rSgnivO/openapi.json
