# Download Images from URLs: Bulk Image & File Downloader (`data-done-right/image-downloader`) Actor

Download images from URLs in bulk: an image downloader API for lists of direct image or file links and for images found on web pages. Dedupe by content hash, convert to JPG, PNG or WebP, resize, filter by size and get public links plus one ZIP.

- **URL**: https://apify.com/data-done-right/image-downloader.md
- **Developed by:** [Data Done Right](https://apify.com/data-done-right) (community)
- **Stats:** 3 total users, 2 monthly users, 95.5% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 files

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 Images from URLs: Bulk Image & File Downloader

Use this tool to **download images from URLs** in bulk. Paste a list of direct image links (or web pages to pull the images from) and get every file saved to a key-value store with a public link, its real type, its size in pixels and a SHA-256 hash. It is a **bulk image downloader** and a general **image downloader API**: it removes duplicates by content, can convert to JPG, PNG or WebP, resize, filter out icons and tracking pixels, and pack everything into one ZIP. No browser, no login and no API key.

> **Rights and fair use.** You are responsible for having the right to download and reuse the images you fetch. Do not use this Actor on sites whose terms forbid automated downloads. It **does not log in, does not send a forged Referer and does not work around hotlink protection**: if a host answers 401, 403 or 429, you get a `failed` row with that reason. For `pageUrls` it reads and obeys the site's `robots.txt`.

### What can this bulk image downloader do

- **Download images from a list of URLs.** One dataset row per URL with `status`: `downloaded`, `duplicate`, `skipped` or `failed`, and a reason when it is not `downloaded`.
- **Download all images from a web page.** Add page links in `pageUrls`: the Actor reads `<img src>` and `srcset` (the largest size of each), `<picture>` sources, lazy `data-src` images, `og:image` and `twitter:image` meta tags and `<link rel="image_src">`, resolves relative links (including `<base href>`) and downloads those images.
- **Know what you really got.** The type is detected from the file's bytes, not the file name or the server header. A `.jpg` link that serves a PNG is saved as `.png` and the row says so; a link that serves a web page is reported as failed instead of being saved as an "image".
- **Filter.** Minimum width and height (drop 1x1 tracking pixels and icons), the image types you want, a size limit per file.
- **Remove duplicates.** Identical files (same SHA-256) are delivered once; later copies get status `duplicate` with a link to the first copy, and are free.
- **Convert and resize.** Convert to JPG, PNG or WebP with Pillow and limit the width, keeping the aspect ratio (never upscaled). JPG gets a white background behind transparency, EXIF rotation is applied, metadata is dropped.
- **One ZIP.** Optionally also get all delivered files in one `images.zip` with a `manifest.csv`, with a public link.
- **Any file type (opt-in).** Turn on `anyFile` to download PDFs, ZIPs, CSVs and other files too. Web pages (HTML) are never saved.

### How to download images from URLs

1. Paste direct image links in **Image or file URLs** (`urls`), one per line, and/or page links in `pageUrls`.
2. Optionally set the image types, a minimum size, `convertTo`, `maxWidth` and `zip`.
3. Run it. Each file is saved in the run's key-value store under `<sha256>.<ext>`, and the dataset lists its public link (`fileUrl`).

### Input example

```json
{
  "urls": [
    "https://upload.wikimedia.org/wikipedia/commons/a/a9/Example.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/3/35/Information_icon.svg",
    "https://picsum.photos/id/237/200/300.jpg",
    "https://picsum.photos/id/10/200/300.webp"
  ],
  "convertTo": "original",
  "zip": true
}
```

More examples: `"pageUrls": ["https://example.com/gallery"], "minWidth": 200` (all larger images of a page), `"convertTo": "webp", "maxWidth": 1600` (smaller files for your site), `"anyFile": true` (PDFs and other files).

### Output

One dataset row per URL:

```json
{
  "url": "https://picsum.photos/id/237/200/300.jpg",
  "finalUrl": "https://fastly.picsum.photos/id/237/200/300.jpg?hmac=...",
  "status": "downloaded",
  "reason": null,
  "contentType": "image/jpeg",
  "sizeBytes": 11615,
  "width": 200,
  "height": 300,
  "sha256": "e5f9164e2ca3cd72c533145fcc1c3adc1b3af8072f0677955ec5217d1f3bceeb",
  "fileUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/e5f9164e...ceeb.jpg",
  "sourcePage": null,
  "key": "e5f9164e2ca3cd72c533145fcc1c3adc1b3af8072f0677955ec5217d1f3bceeb.jpg",
  "index": 3
}
```

- `status`: `downloaded` (charged), `duplicate` (same content as an earlier file or the same URL listed twice), `skipped` (your rules: type, size, minimum dimensions, spending limit, robots.txt) or `failed` (network error, HTTP error, not a valid image, blocked address).
- `reason`: why a URL was not downloaded, or a note on a downloaded file (for example `converted png to jpg; resized to 800px wide`, or `URL ends in .jpg but the file is png; saved as .png`).
- `sha256`, `sizeBytes`, `width`, `height`, `contentType` describe the **delivered** file (after any conversion). The key is `sha256` plus the real extension, so it is always safe and never contains parts of the URL.
- `sourcePage` is the page an image was found on (`pageUrls`), empty for URLs you listed yourself.
- The `SUMMARY` record has the counts, the charged units, `zipUrl` and any notes.

### Image downloader API

Call this Actor from the Apify API, Python, JavaScript, Make, Zapier or n8n. Example with the Apify Python client:

```python
from apify_client import ApifyClient

run = ApifyClient("<APIFY_TOKEN>").actor("data-done-right/image-downloader").call(run_input={
    "urls": ["https://example.com/a.jpg", "https://example.com/b.png"],
    "convertTo": "webp", "maxWidth": 1600, "zip": True})
client = ApifyClient("<APIFY_TOKEN>")
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["status"], row["url"], row["fileUrl"])
```

Files are read back from the run's key-value store (`client.key_value_store(run["defaultKeyValueStoreId"])`) or by their `fileUrl`.

### Use it in Claude, ChatGPT and Cursor

Connect Apify's MCP server (mcp.apify.com) and your AI assistant can fetch images for you. For example: "download the images from this list of URLs as WebP, skip anything under 300 pixels wide, and give me the ZIP link". The only input you need is a list of image URLs (`urls`) or a page URL (`pageUrls`).

### Use cases

- **Archive or migrate images:** turn a spreadsheet of image links into files you own, with hashes to verify them.
- **Build datasets:** collect images for machine-learning training sets, with duplicates removed and tiny images filtered out.
- **Prepare images for a website or store:** convert a folder of mixed formats to WebP and limit the width.
- **Check links:** see which image URLs are dead, are not images, or are too large.
- **Product and media catalogs:** download the product images listed on your own pages.

### How much does it cost

Pay per event, only for files that are delivered:

- **$0.0015 per downloaded file** (`file`). 100 small images cost about $0.15.
- **$0.0002 per started MB beyond the first MB** of each file (`megabyte`), so large files cost fairly. A 4.2 MB file costs $0.0023; a file of 1 MB or less costs just the file price. 1 MB is 1,000,000 bytes, measured on the delivered file.
- **Free:** duplicates, skipped and failed URLs, the ZIP, the dataset and the `SUMMARY` record.

Paid Apify plans get 10% (Silver) to 20% (Gold and up) off. `maxFiles` caps the number of URLs, `maxFileSizeMb` caps each file, and if you set a maximum spend per run the Actor stops cleanly when it is reached, keeping the files already delivered (the remaining URLs get a `skipped` row).

### FAQ

#### How do I download images from a list of URLs?

Put the links in `urls` and run. Every link gets a row; the files that were delivered have a `fileUrl` you can open or download. Turn on `zip` to also get everything in one ZIP.

#### How do I download all images from a web page?

Put the page link in `pageUrls`. The Actor fetches the page (respecting its robots.txt), finds the images and downloads them. Images are taken from the HTML as served, so images that a page only creates with JavaScript after loading are not found. Use `minWidth` to drop icons and `maxFiles` to limit the count. Images found on a page are fetched like any other image link; `robots.txt` is applied to the page itself, not to the image files it points to.

#### Does it respect robots.txt?

For `pageUrls`, yes: a page that `robots.txt` disallows for the `ImageDownloader` agent (or `*`) is not fetched and gets a `skipped` row. If `robots.txt` cannot be read because of a server error, the page is not fetched either. Direct file links you list yourself in `urls` are not checked against robots.txt, because you chose them; you are responsible for the right to download them.

#### Can it get images behind a login or bypass hotlink protection?

No. It sends no cookies, credentials or Referer, and it does not pretend to be a browser. Links with a user name or password in the URL are refused. Pages of login-walled social sites (Instagram, Facebook, LinkedIn, X, TikTok) are not supported. A host that refuses the download gives a `failed` row with the HTTP status.

#### Which image formats are supported?

JPG, PNG, WebP, GIF, AVIF, SVG, BMP and TIFF. The type is read from the file's bytes. SVG files that contain scripts, event handlers or embedded pages are not saved. With `convertTo` set, SVG files are kept as SVG (they are vector). Animated GIF or WebP files keep only their first frame when they are converted or resized; without conversion the original animation file is delivered untouched.

#### How are duplicates found?

By the SHA-256 of the downloaded bytes, and again by the hash of the delivered file after conversion (two different sources that convert to the same output also count). The same URL listed twice is fetched only once.

#### How big can a file be?

25 MB by default, up to 100 MB with `maxFileSizeMb`. The limit is checked on the `Content-Length` header and again while streaming, so an oversized or endless response is cut off early. Images over 100 megapixels are skipped, and conversion is only done for images up to 36 megapixels.

#### How long are the file links kept?

Files are saved in the run's default key-value store, which expires after your plan's data retention period like other unnamed storages. Set `outputStoreName` to save them in a named store that stays until you delete it. Anyone who has a `fileUrl` can open it.

#### Is it safe to give it any URL?

Links to localhost, private networks, link-local and cloud-metadata addresses are blocked, including names that resolve to them and redirects into them (every redirect hop is checked again; up to 5 are followed). When you use a proxy, the proxy opens the connection, but the same address checks still run before every request.

#### Is it polite to the servers?

Requests to the same host start at least `perHostDelayMs` apart (250 ms by default) and at most 3 run at once per host. So a higher `concurrency` only speeds up lists that are spread across several hosts. The `timeoutSecs` clock for a file starts when its turn at the host comes, not while it waits in line. Network errors, timeouts and 429/5xx answers are retried up to 3 times with backoff, and `Retry-After` is honored. 401, 403 and 404 are not retried.

#### Do I need an API key or login?

No.

### Related tools

- **All-in-One File Converter: PDF, Office, Images, Audio & Video** converts the files you download to other formats.
- **Video Frame Extractor: Keyframes, Scenes & Thumbnails for AI** turns video files into images.
- **PDF to Text & Markdown Converter with Automatic OCR** reads the text in the PDFs and images you download.
- **Merge Datasets & Dedupe: Combine, Filter, Export CSV & Excel** combines the result rows of several runs.

# Actor input Schema

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

Direct links to the images (or files) to download, one per line. Example: https://example.com/photo.jpg. Use only files you have the right to download and reuse.

## `pageUrls` (type: `array`):

Web pages whose images you want: the Actor reads \<img src/srcset>, og:image and image_src links and downloads those images. Example: https://example.com/gallery. The page's robots.txt is respected; login-walled social sites are not supported.

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

Most URLs processed in this run (listed and extracted from pages together). URLs beyond this are not fetched and not charged.

## `maxFileSizeMb` (type: `number`):

Files larger than this are skipped, checked from the headers and again while downloading. 1 MB = 1,000,000 bytes.

## `minWidth` (type: `integer`):

Skip images narrower than this. 0 = no filter. Example: 200 drops icons and tracking pixels.

## `minHeight` (type: `integer`):

Skip images shorter than this. 0 = no filter.

## `allowedTypes` (type: `array`):

The type is detected from the file's bytes, not its name. Other types are skipped. Ignored when 'Any file type' is on.

## `anyFile` (type: `boolean`):

Also download PDFs, ZIPs, CSVs and other files. Web pages (HTML) are never saved: use pageUrls to extract images from a page.

## `convertTo` (type: `string`):

Keep the original files, or convert JPG, PNG, WebP, GIF, AVIF, BMP and TIFF images with Pillow. Transparency is flattened onto white for JPG. SVG files are kept as they are. Animations keep only the first frame.

## `maxWidth` (type: `integer`):

Shrink images wider than this, keeping the aspect ratio (never upscales). 0 = original size. Images that are resized are re-encoded.

## `quality` (type: `integer`):

1 to 100, used when images are converted or resized to JPG or WebP.

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

Files with identical content (SHA-256) are delivered once; later copies get status duplicate and are not charged.

## `zip` (type: `boolean`):

Also save all delivered files in one images.zip in the key-value store, with a public link in the SUMMARY record. The ZIP itself is free.

## `zipMaxMb` (type: `number`):

Files that would push the ZIP over this size are still saved and listed, but left out of the ZIP.

## `outputStoreName` (type: `string`):

Optional key-value store name. Named stores are kept until you delete them, while a run's own store expires after your plan's retention period.

## `concurrency` (type: `integer`):

How many files are downloaded at the same time.

## `perHostDelayMs` (type: `integer`):

Minimum time between two requests to the same website, so a big list does not hammer one server. At most 3 run at once per host.

## `timeoutSecs` (type: `integer`):

Time allowed for each download attempt, counted from when it gets its turn at its host (see the per-host delay). Failed requests are retried up to 3 times with backoff.

## `maxPages` (type: `integer`):

How many pageUrls pages are read. Further pages get a skipped row.

## `allSrcsetSizes` (type: `boolean`):

By default only the largest image of each srcset is taken. Turn on to download every size listed.

## `proxyConfiguration` (type: `object`):

Optional. Send downloads through Apify Proxy or your own proxies. Usually not needed. Each retry uses a new session (new IP).

## Actor input object example

```json
{
  "urls": [
    "https://upload.wikimedia.org/wikipedia/commons/a/a9/Example.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/3/35/Information_icon.svg",
    "https://picsum.photos/id/237/200/300.jpg",
    "https://picsum.photos/id/10/200/300.webp"
  ],
  "maxFiles": 100,
  "maxFileSizeMb": 25,
  "minWidth": 0,
  "minHeight": 0,
  "allowedTypes": [
    "jpg",
    "png",
    "webp",
    "gif",
    "avif",
    "svg",
    "bmp",
    "tiff"
  ],
  "anyFile": false,
  "convertTo": "original",
  "maxWidth": 0,
  "quality": 85,
  "dedupe": true,
  "zip": true,
  "zipMaxMb": 100,
  "concurrency": 5,
  "perHostDelayMs": 250,
  "timeoutSecs": 60,
  "maxPages": 20,
  "allSrcsetSizes": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One row per URL (the default dataset), as JSON, CSV, Excel and more.

## `records` (type: `string`):

Key-value store records: the downloaded files, the run summary (SUMMARY) and the ZIP (images.zip) if requested.

# 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/a/a9/Example.jpg",
        "https://upload.wikimedia.org/wikipedia/commons/3/35/Information_icon.svg",
        "https://picsum.photos/id/237/200/300.jpg",
        "https://picsum.photos/id/10/200/300.webp"
    ],
    "convertTo": "original",
    "zip": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("data-done-right/image-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/a/a9/Example.jpg",
        "https://upload.wikimedia.org/wikipedia/commons/3/35/Information_icon.svg",
        "https://picsum.photos/id/237/200/300.jpg",
        "https://picsum.photos/id/10/200/300.webp",
    ],
    "convertTo": "original",
    "zip": True,
}

# Run the Actor and wait for it to finish
run = client.actor("data-done-right/image-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/a/a9/Example.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/3/35/Information_icon.svg",
    "https://picsum.photos/id/237/200/300.jpg",
    "https://picsum.photos/id/10/200/300.webp"
  ],
  "convertTo": "original",
  "zip": true
}' |
apify call data-done-right/image-downloader --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,data-done-right/image-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/LZ7rL5NqHBVUUvjiy/builds/3c2zesyj9x8s3Aztv/openapi.json
