# Image Downloader: Bulk Images to ZIP, WebP & Audit (`urlwright/image-downloader`) Actor

Download every image from pages, sitemaps, CSV/Shopify exports or datasets. ZIP, WebP/AVIF, renaming by SKU, alt-text and size audit. GPS data always removed. Failed, blocked and duplicate images are free.

- **URL**: https://apify.com/urlwright/image-downloader.md
- **Developed by:** [Urlwright](https://apify.com/urlwright) (community)
- **Categories:** Developer tools, SEO tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 image 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

## Image Downloader: Bulk Images to ZIP, WebP & Audit

**Urlwright** — *Little tools for the web.*

Download every image from pages, sitemaps, CSV/Shopify exports or Apify datasets. One dataset row per image with alt text, dimensions, hashes and audit flags. Optional ZIP parts, WebP/AVIF conversion, renaming by SKU, and a diff mode that only fetches new or changed files.

Default User-Agent: `UrlwrightBot/1.0 (image-downloader; +https://urlwright.com/bot)` (`UrlwrightBot` for robots.txt matching).

### What it does

Give it page URLs, direct image URLs, a sitemap, a domain, a CSV/Shopify export, or the dataset from a [Sitemap URL Extractor](https://apify.com/urlwright/sitemap-url-extractor) run. For each discovered image you get one row with:

1. **Discovery** – `img`, `srcset`/`picture`, lazy-load attributes, `<noscript>`, Open Graph/Twitter cards, CSS backgrounds (inline and linked stylesheets), icons, image links, image sitemaps, CSV columns and dataset fields. Each hit records `foundIn` and the page URL(s).
2. **Download or list** – Full download with magic-byte validation, SHA-256 and perceptual hash (dHash), or **audit mode** that lists URLs and metadata without storing files (optional partial fetch for dimensions).
3. **Files** – Individual files in the run key-value store (public links), and/or streamed **ZIP** parts (STORE, no re-compression) split by size. Template-based renaming (`{sku}`, `{handle}`, `{domain}`, `{pathSlug}` = image URL file stem, `{pageSlug}`, `{position}`, …). Case and Unicode letters are kept in ZIP/`fileName`; KV keys are ASCII-safe.
4. **Processing** – Optional convert to WebP/AVIF/JPEG/PNG, resize, thumbnails, safe SVG rasterization. GPS, author, serial and similar EXIF/XMP fields are **always** stripped; orientation can be preserved.
5. **Audit** – `missingAlt`, `oversized`, `legacyFormat`, `webpSavingsEstimate`, `exifFound`, `gpsFound` (boolean only, never coordinates). Aggregated `REPORT.md` / `REPORT.json` and optional `image-sitemap.xml`.
6. **Diff** – With `onlyNew`, unchanged images return `unchanged` and cost only `image-listed`; removed URLs get `status: removed`.

Polite by default: robots.txt on page and image hosts, at most 1 request per second per host, honest User-Agent, Referer set to the discovering page.

### Good for

**Shop migration & SKU workflows** – Shopify CSV preset (`Image Src`, `Variant Image`, Handle, SKU, alt text) plus `{sku}` / `{handle}` in file names · **Agency relaunch backups** – whole-site sitemap runs with alt text preserved in the dataset · **SEO & accessibility audits** – missing alt, heavy PNG/JPEG, WebP savings estimate without downloading every byte · **AI / ML datasets** – hashes, dimensions and source URLs from sites you are licensed to use · **Archives & change tracking** – scheduled runs with `onlyNew` and `stateKey` to avoid duplicate storage.

### Input examples

Store console prefill: `startUrls: [{ "url": "https://books.toscrape.com/" }]`, `maxImages: 20`.

**Homepage scan (default):**

```json
{
  "startUrls": [{ "url": "https://books.toscrape.com/" }],
  "maxImages": 50,
  "output": "both",
  "zipGroupBy": "page",
  "dedupeSimilar": true
}
```

**Shopify export (public CSV URL):**

```json
{
  "csvUrl": "https://example.com/exports/products.csv",
  "csvPreset": "shopify",
  "fileNameTemplate": "{handle}/{sku}-{position}",
  "maxImages": 5000
}
```

**Whole site via sitemap:**

```json
{
  "domains": ["example.com"],
  "includePathGlobs": ["/products/**"],
  "maxPages": 500,
  "crawl": { "enabled": false }
}
```

**Sitemap URL Extractor dataset:**

```json
{
  "sitemapDatasetId": "YOUR_DATASET_ID",
  "maxPages": 1000,
  "renderJs": false
}
```

**Audit only (no file storage):**

```json
{
  "startUrls": [{ "url": "https://example.com/" }],
  "mode": "audit",
  "auditDimensions": true,
  "output": "files"
}
```

**Incremental backup:**

```json
{
  "domains": ["example.com"],
  "onlyNew": true,
  "stateKey": "prod-images-weekly",
  "maxImages": 10000
}
```

- `startUrls` – HTML pages to scan (also seeds optional same-domain crawl).
- `imageUrls` – direct image URLs, no page fetch.
- `csvUrl` / `csvText` – CSV, TSV, XLSX or public Google Sheets URL; `csvPreset: shopify` maps standard export columns.
- `datasetId` + `datasetField` – any Apify dataset column with URLs.
- `sitemapUrls` / `domains` – XML or gzip sitemaps; domains discover sitemaps from `robots.txt` then `/sitemap.xml`.
- `sitemapDatasetId` – rows from Sitemap URL Extractor (`url` field; skips `changeStatus=removed` and `statusCode≥400`).
- `renderJs` – Playwright render instead of HTTP fetch; bills `page-rendered` instead of `page-scanned`. Images/fonts/media blocked in the browser; only DOM needed.
- `onlyNew` + `stateKey` – named KV store `image-downloader-state` holds ETag/Last-Modified/sha256 for conditional requests.

Recommended memory: **1024 MB** (default). Use **2048 MB** or more for large shops, heavy `renderJs`, or high `maxImages`.

Social and marketplace platform URLs are rejected at input validation (use your own site, CSV, or direct image URLs).

### Output

#### Dataset row (shortened example)

```json
{
  "schemaVersion": "1.0",
  "imageUrl": "https://books.toscrape.com/media/cache/.../cover.jpg",
  "finalUrl": "https://books.toscrape.com/media/cache/.../cover.jpg",
  "pageUrls": ["https://books.toscrape.com/"],
  "firstPageUrl": "https://books.toscrape.com/",
  "foundIn": ["img"],
  "status": "ok",
  "skipReason": null,
  "format": "jpeg",
  "bytes": 8934,
  "width": 189,
  "height": 280,
  "alt": "A Light in the Attic",
  "sha256": "…",
  "phash": "…",
  "fileName": "books.toscrape.com/cover.jpg",
  "fileKey": "files/books.toscrape.com/cover.jpg",
  "fileUrl": "https://api.apify.com/v2/key-value-stores/…",
  "zipPart": 1,
  "missingAlt": false,
  "oversized": false,
  "legacyFormat": false,
  "webpSavingsEstimate": { "bytes": 4200, "percent": 47 },
  "exifFound": false,
  "gpsFound": false,
  "charged": true,
  "chargedEvents": { "image-downloaded": 1, "page-scanned": 0 }
}
```

#### Statuses

`ok` (downloaded) · `listed` (audit list) · `unchanged` (diff, HTTP 304) · `skipped` (free when `skipReason` is set) · `failed` · `removed` (diff: URL gone since last run). Each row has `charged: true|false` and optional `skipReason`.

Common free `skipReason` values: `robots-blocked`, `blocked-403`, `rate-limited-429`, `not-found-404`, `not-an-image`, `duplicate-exact`, `duplicate-similar`, `filtered-format`, `filtered-size`, `icon-or-pixel`, `too-large`, `timeout`, `ssrf-blocked`, `platform-not-supported`, `limit-reached`.

#### Key-value store

| Key | Content |
|---|---|
| `RUN_SUMMARY` | Totals, `byStatus`, `bySkipReason`, `chargedEvents`, `truncated`, `stoppedByChargeLimit` |
| `REPORT.json` / `REPORT.md` | Audit and size summary |
| `image-sitemap.xml` | Generated image sitemap from successful rows |
| `images-part-N.zip` | ZIP parts when `output` is `zip` or `both` |
| `files/…` | Individual images and derivatives |

Diff state (not in the default store): named store `image-downloader-state`, key from `stateKey`.

### Pricing

Pay per event (USD). **No start fee.** Recommended minimum **Max total charge** per run: **$0.02**.

| Event | When | FREE | BRONZE | SILVER | GOLD |
|---|---|---:|---:|---:|---:|
| `image-downloaded` (primary) | Image validated, measured and stored (or fully measured in audit mode) | 0.0020 | 0.0017 | 0.0014 | 0.0010 |
| `page-scanned` | HTML page fetched and parsed (sitemap pages too) | 0.0010 | 0.0009 | 0.0008 | 0.0006 |
| `page-rendered` | Same as page-scanned when `renderJs` is on | 0.0050 | 0.0045 | 0.0040 | 0.0030 |
| `image-listed` | Audit list only, or diff `unchanged` | 0.0005 | 0.00045 | 0.00040 | 0.00030 |
| `image-processed` | Each derivative (convert, resize, thumbnail) | 0.0006 | 0.0005 | 0.0004 | 0.0003 |
| `large-image-mb` | Each started MB after the first MB of a stored image | 0.0009 | 0.0008 | 0.0007 | 0.0006 |
| `zip-100mb` | Each started 100 MB of ZIP output | 0.010 | 0.009 | 0.008 | 0.0075 |

**Per 1,000 (FREE → GOLD):** `image-downloaded` **$2.00 → $1.00** · `page-scanned` **$1.00 → $0.60** · `page-rendered` **$5.00 → $3.00** · `image-listed` **$0.50 → $0.30** · `image-processed` **$0.60 → $0.30**. Platinum and Diamond pay the Gold price.

**Bronze example:** 500 images from one sitemap, one page each, no processing: `500 × 0.0017 + 500 × 0.0009 = $1.30`.

### What is free

You do not pay for:

- Failed fetches (timeout, HTTP errors, 403/429, redirect limit).
- `robots.txt` disallowed URLs (pages and images).
- Responses that are not images (HTML under an image URL, empty or truncated bodies).
- Exact or perceptual duplicates (the larger variant is kept).
- Filtered-out formats, sizes or URL patterns; icons and tracking pixels.
- Pages that fail or return no discoverable images (no page event).
- Skipped rows stopped by your max-image or charge limits (still listed with `skipReason`).

Every skipped row remains in the dataset with `charged: false` and a `skipReason` where applicable.

### Fair use & privacy

**Use responsibly.** Images on the web are usually protected by copyright and may show people. Image Downloader only fetches publicly accessible files from URLs you provide, respects robots.txt, and does not log in or bypass any protection. You are responsible for having the rights or a legal basis (for example your own website, a licence, or a permitted exception) for how you use the downloaded files. Location, author and device data in EXIF are always removed.

- **Robots & rate limits** – `UrlwrightBot` rules apply to pages and image hosts; crawl-delay is honored (capped at 10 s). If `robots.txt` is missing (404), fetches proceed; server errors or timeouts on `robots.txt` are treated as disallow-all for that origin.
- **SSRF** – Hostnames are resolved before connect; private, link-local and metadata IPs are blocked, including after redirects.
- **No platform scraping** – Instagram, marketplaces, Google Images and similar inputs are rejected.
- **Dataset** – Alt text and titles from the source page are stored; GPS coordinates are never written. `gpsFound` is true/false only.
- **Cookies** – The actor does not send session cookies or log in.

More tools: [urlwright.com](https://urlwright.com) · Bot details: [urlwright.com/bot](https://urlwright.com/bot)

### Limitations

- **Passive only** – No login, cookies, captcha bypass or proxy rotation. Strict bot protection may yield free `blocked-403` rows.
- **Politeness** – One request per second per host limits speed on single-origin bulk jobs; use direct `imageUrls` or CSV when you already have URLs.
- **JavaScript** – Without `renderJs`, client-rendered image URLs may be missing. Rendering costs more (`page-rendered`) but still blocks image bytes in the browser.
- **Size** – Default max file size 50 MB; configurable via `maxFileMB`. HTML pages are read up to 5 MB.
- **Similar-image dedupe** – dHash compares appearance; near-duplicates below your threshold may still appear twice.

### FAQ

#### Why is my row `skipped` with `robots-blocked`?

The image or page URL is disallowed for `UrlwrightBot` in `robots.txt` (or robots could not be fetched safely). The row is **free**. Fix the rule on your site or provide direct image URLs you are allowed to fetch.

#### How do I migrate a Shopify catalog?

Export products to CSV, set `csvUrl` and `csvPreset: "shopify"`, tune `fileNameTemplate` (e.g. `{handle}/{sku}-{position}`), and raise `maxImages`. Variant and product image columns are merged automatically. `{pathSlug}` is the image URL’s file name (without extension), not the page slug — use `{pageSlug}` for the page.

#### What does audit mode cost?

Each listed image bills `image-listed` (not `image-downloaded`). With `auditDimensions: true`, a small byte range is fetched to read dimensions; storage events still apply only when files are written.

#### How does `onlyNew` work?

State is stored under `stateKey` in the `image-downloader-state` store. Unchanged images respond with HTTP 304 and status `unchanged` (`image-listed`). New or changed bytes bill `image-downloaded` again. URLs that disappear from the crawl get `status: removed` (free).

#### Can I chain Sitemap URL Extractor?

Yes. Run Sitemap URL Extractor, pass `sitemapDatasetId`, optionally filter with `includePathGlobs`, then scan up to `maxPages` HTML pages for embedded images (plus image sitemap URLs when present).

#### Does rendering download every asset?

No. Playwright blocks image, font and media requests; only HTML/JS needed for the DOM is loaded. You still pay `page-rendered` per successful page with at least one discovered image.

#### Where are ZIP files?

In the run key-value store as `images-part-1.zip`, … Split size defaults to 1024 MB (`zipPartMB`). Billing uses `zip-100mb` on the compressed part size after the run writes each part.

#### Are EXIF and GPS kept?

No. Author, GPS, serial numbers and similar metadata are stripped on every stored image. Audit fields may report `exifFound` / `gpsFound` before strip; coordinates are never exported.

# Actor input Schema

## `startUrls` (type: `array`):

HTML pages to scan for images (img, srcset, picture, lazy attrs, og/twitter, CSS, icons, image links).

## `imageUrls` (type: `array`):

Image URLs to download without page discovery.

## `csvUrl` (type: `string`):

Public URL of a CSV, TSV, XLSX file, or a public Google Sheets document (rewritten to CSV export).

## `csvText` (type: `string`):

Inline CSV or TSV with a header row. Use with csvUrlColumn or csvPreset.

## `csvUrlColumn` (type: `string`):

Column name containing image URLs. Leave empty to auto-detect (Image Src, url, image, src, …).

## `csvPreset` (type: `string`):

Shopify: uses Image Src / Variant Image and carries Handle, Variant SKU, Image Alt Text, Image Position into template vars.

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

Dataset whose rows supply image or page URLs (field from datasetField).

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

Field name on dataset rows that holds the URL.

## `sitemapUrls` (type: `array`):

Sitemap or sitemap-index URLs (XML or .gz). Image sitemaps yield image URLs directly.

## `domains` (type: `array`):

Discover sitemaps via robots.txt Sitemap: lines, then /sitemap.xml fallback.

## `sitemapDatasetId` (type: `string`):

Dataset from urlwright/sitemap-url-extractor. Uses field url; skips changeStatus=removed and statusCode≥400.

## `includePathGlobs` (type: `array`):

Only crawl/scan page paths matching these globs (e.g. /products/\*\*).

## `excludePathGlobs` (type: `array`):

Skip page paths matching these globs.

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

Hard cap on HTML pages scanned (sitemap + crawl + startUrls).

## `crawl` (type: `object`):

Optional bounded crawl from startUrls on the same registrable domain.

## `renderJs` (type: `boolean`):

Load pages with Playwright (DOM only; images/fonts/media blocked). Recommend 4096 MB memory. Charges page-rendered instead of page-scanned.

## `renderScroll` (type: `boolean`):

Scroll the page to trigger lazy-loaded images when renderJs is on.

## `renderWaitMs` (type: `integer`):

Extra wait after load/scroll before reading the DOM.

## `allSrcsetVariants` (type: `boolean`):

If false (default), only the largest srcset/picture candidate is kept per element.

## `formats` (type: `array`):

Empty = all supported. Values: jpeg, png, webp, avif, gif, svg, ico, heic, bmp, tiff.

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

Skip images narrower than this after measurement.

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

Skip images shorter than this after measurement.

## `minBytes` (type: `integer`):

Skip smaller files after download.

## `maxBytes` (type: `integer`):

Skip larger files after download (0 = no filter). Separate from maxFileMB abort.

## `includeUrlPatterns` (type: `array`):

Glob or /regex/ patterns. Empty = include all.

## `excludeUrlPatterns` (type: `array`):

Glob or /regex/ patterns to skip.

## `skipIcons` (type: `boolean`):

Drop favicons, ≤2×2 pixels, known tracking-pixel paths and placeholders.

## `dedupeSimilar` (type: `boolean`):

Drop near-duplicates via dHash; keep the largest variant.

## `similarityThreshold` (type: `integer`):

Max Hamming distance for perceptual duplicates (default 4).

## `maxImagesPerPage` (type: `integer`):

Cap discoveries kept from a single page (0 = unlimited).

## `maxImages` (type: `integer`):

Hard cap on images processed in this run.

## `maxTotalMB` (type: `integer`):

Stop downloading when cumulative stored bytes reach this (0 = unlimited).

## `maxFileMB` (type: `integer`):

Abort a download exceeding this size (skipReason too-large, free).

## `mode` (type: `string`):

download stores files; audit lists images (image-listed) without full download.

## `auditDimensions` (type: `boolean`):

In audit mode, fetch the first 64 KB (Range) to read pixel size via image-size.

## `onlyNew` (type: `boolean`):

Compare against previous state in named KV store image-downloader-state. Unchanged → image-listed; removed rows reported free.

## `stateKey` (type: `string`):

Record key inside image-downloader-state. Required when onlyNew is true.

## `output` (type: `string`):

files = KV records only; zip = ZIP parts only; both = files + ZIP.

## `zipGroupBy` (type: `string`):

Folder layout inside ZIP archives.

## `zipPartMB` (type: `integer`):

Split ZIP when a part would exceed this (max 1024).

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

Vars: {domain} {pathSlug} (image URL file stem) {pageSlug} (page path) {index} {position} (Shopify Image Position or 1-based order) {hash8} {width} {height} {altSlug} {ext} {sku} {handle} {csv:Column Name}. Case and Unicode letters are kept in file names; only / \ : \* ? " < > | and control chars are replaced (whitespace → -). Extension is appended automatically.

## `keepOrientation` (type: `boolean`):

When stripping EXIF, write a minimal Orientation tag if present (default true). GPS/author/serial are always removed.

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

Derive a converted file (charges image-processed). none = no conversion.

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

Quality for lossy convertTo (1–100).

## `keepOriginal` (type: `boolean`):

If true, store both the original and the converted file. If false, store only the converted file (fileKey points to it).

## `resizeMaxWidth` (type: `integer`):

Downscale so width ≤ this (0 = no resize).

## `resizeMaxHeight` (type: `integer`):

Downscale so height ≤ this (0 = no resize).

## `thumbnails` (type: `array`):

Extra thumbnail widths in px (first frame for animated GIF/WebP). Each thumbnail charges image-processed.

## `rasterizeSvg` (type: `boolean`):

After sanitising SVG, also store a PNG raster (safe, no external refs).

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

Per-request timeout for pages and images.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://books.toscrape.com/"
    }
  ],
  "csvUrlColumn": "",
  "csvPreset": "none",
  "datasetField": "url",
  "maxPages": 1000,
  "renderJs": false,
  "renderScroll": true,
  "renderWaitMs": 500,
  "allSrcsetVariants": false,
  "formats": [],
  "minWidth": 0,
  "minHeight": 0,
  "minBytes": 0,
  "maxBytes": 0,
  "skipIcons": true,
  "dedupeSimilar": true,
  "similarityThreshold": 4,
  "maxImagesPerPage": 0,
  "maxImages": 20,
  "maxTotalMB": 0,
  "maxFileMB": 50,
  "mode": "download",
  "auditDimensions": false,
  "onlyNew": false,
  "stateKey": "default",
  "output": "both",
  "zipGroupBy": "none",
  "zipPartMB": 1024,
  "fileNameTemplate": "{domain}/{pathSlug}",
  "keepOrientation": true,
  "convertTo": "none",
  "quality": 80,
  "keepOriginal": true,
  "resizeMaxWidth": 0,
  "resizeMaxHeight": 0,
  "thumbnails": [],
  "rasterizeSvg": false,
  "requestTimeoutSecs": 20
}
```

# Actor output Schema

## `images` (type: `string`):

Dataset items: downloaded or listed images with audit fields, hashes and file links.

## `runSummary` (type: `string`):

JSON summary of the run (status counts, skip reasons, charged events).

## `report` (type: `string`):

Totals, heaviest images, missing-alt quota, format distribution.

## `reportMd` (type: `string`):

Human-readable run report.

## `imageSitemap` (type: `string`):

image-sitemap.xml with image:image entries per page.

# 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 = {
    "startUrls": [
        {
            "url": "https://books.toscrape.com/"
        }
    ],
    "maxImages": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("urlwright/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 = {
    "startUrls": [{ "url": "https://books.toscrape.com/" }],
    "maxImages": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("urlwright/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 '{
  "startUrls": [
    {
      "url": "https://books.toscrape.com/"
    }
  ],
  "maxImages": 20
}' |
apify call urlwright/image-downloader --silent --output-dataset

```

## MCP server setup

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