# Bulk Image Downloader & Optimizer: Resize, WebP, EXIF (`ataism/image-optimizer`) Actor

Download direct image URLs or uploads, resize, convert to WebP/AVIF/JPEG/PNG, strip metadata, SHA-256 and perceptual hash. $2 per 1,000 images.

- **URL**: https://apify.com/ataism/image-optimizer.md
- **Developed by:** [Kaizoku Data](https://apify.com/ataism) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 image processeds

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 Image Downloader & Optimizer: Resize, WebP, EXIF

Download the images **you list or upload**, resize them, convert to WebP, AVIF, JPEG or PNG, strip metadata and get a public link for each result, plus size, savings, dimensions, SHA-256 and a perceptual hash. Keyless, no browser, no proxy.

**Price: $2 per 1,000 images ($0.002 per image saved).** Big images add **$0.001 per large-image unit**: one unit per started 10 MB of file above the first 5 MB, per started 16 megapixels above the first 16 MP, or per started 10 MB of **saved output** above the first 5 MB, whichever is most (a 25 MB file is $0.002 + 2 x $0.001 = $0.004). No start fee on top of Apify's default. Only images that are saved are charged.

- **Resize and convert:** max width and/or height ("fit inside", never enlarged), auto-rotated by EXIF orientation, output WebP, AVIF, JPEG, PNG or the input's own format, with a quality setting.
- **Metadata stripped by default:** the saved file carries no EXIF, XMP, comments or GPS. An option keeps the colour profile and basic camera fields. **GPS is never returned and never written to an output file**, even if you ask.
- **A row per image:** public link, key, input and output size, savings %, dimensions, colour space, alpha, SHA-256 (source and output) and a 64-bit perceptual hash (dHash) for finding duplicates.
- **Direct image URLs and uploads only:** it does not crawl pages. Private addresses and social-media or platform hosts are refused, and each host's robots.txt is checked.
- **Quick start:** press *Start*. The prefilled input downloads three public-domain NASA images from Wikimedia Commons and saves them as 800 px WebP (about 1 second, $0.006).

### What it does

For each image URL (or uploaded file):

1. Checks the host's `robots.txt` for the image path (our User-Agent is `image-optimizer-actor/0.1 (https://apify.com/ataism/image-optimizer)`). A disallowed path is **not downloaded** and gets the error `blocked-by-robots`, uncharged. A `Crawl-delay` in that robots.txt is honoured in full (never shortened): downloads from that host are spaced at least that far apart, counted from when its robots.txt was answered; over 2 seconds the host is skipped (free `slow-host` rows) unless you set `allowSlowHosts`, over 3.5 seconds always.
2. Downloads it (public addresses only, at most 5 redirects, each hop re-checked, size cap, timeouts). The first bytes must be an image: **the file's bytes decide, not the extension or Content-Type**. JPEG, PNG, WebP, GIF, AVIF and TIFF are accepted. SVG, PDF, web pages and everything else are rejected after a few KB.
3. Reads dimensions, format, colour space, transparency and EXIF orientation; computes SHA-256 and the perceptual hash of the source.
4. Rotates upright (EXIF orientation), optionally resizes, converts, and removes metadata.
5. Saves the result in the run's key-value store and returns one dataset row with its public link.

**HEIC/HEIF photos are not supported** (the image library has no HEVC decoder). They come back as `unsupported-format`, uncharged. AVIF is supported as input and output.

### Input

| Field | What it does |
|---|---|
| **Image URLs** | Direct `http(s)` links to image files, one per line. Default: three public-domain sample images. |
| **Uploaded image files** | Upload from your computer to a key-value store (the field offers this), or paste a record URL. See *Using your own files* below. |
| **Output format** | `webp` (default), `avif`, `jpeg`, `png` (always lossless) or `original` (keep each file's format; GIF stays GIF, TIFF stays TIFF). |
| **Quality** | 1-100, default 80. For WebP, AVIF and JPEG. Not used for PNG. |
| **Max width / Max height** | Resize to fit inside this box, keeping the aspect ratio. Never enlarges. `0` = no limit. Maximum 16,384 px. |
| **Keep metadata** | Off by default. On keeps the **colour profile and camera make, model and capture date** only. GPS, comments, XMP and thumbnails are always removed. |
| **Return EXIF details** | Adds an `exif` object: camera make, model, capture date (camera local time) and orientation. **Never contains GPS.** `gpsRemoved: true` only says that location data existed in the source and was dropped. |
| **Max file size (MB)** | Default 25, maximum 50. Larger files are skipped with `too-large`, uncharged. |
| **Allow slow hosts** (`allowSlowHosts`) | Off by default. Off: images on a host whose robots.txt asks for a `Crawl-delay` over 2 seconds are skipped with free `slow-host` rows. On: hosts with a delay of 2 to 3.5 seconds are downloaded and the delay is honoured in full between every request to that host, so the run takes longer; hosts that ask for more than 3.5 seconds are still skipped (free `slow-host` rows, "over the 3.5 s maximum this Actor honours"). The waiting is billed as run time on your account: at a 3.5 second delay one image costs about 9.7% of its price in compute at $0.20 per compute unit. |
| **Download timeout** | Per image, default 60 s. Time spent waiting for a `Crawl-delay` is not counted. |

Animated GIF and WebP stay animated when the output is WebP or GIF **and the frames total at most 16 megapixels** (frames x width x height). For other output formats, or above 16 MP, only the first frame is saved (`animatedFirstFrameOnly: true`).

#### Example inputs

**1. Web thumbnails: 1,200 px WebP, metadata stripped:**

```json
{
  "imageUrls": ["https://example.com/photos/a.jpg", "https://example.com/photos/b.png"],
  "outputFormat": "webp",
  "quality": 80,
  "maxWidth": 1200
}
```

**2. Duplicate detection: hashes only matter, keep size small, camera details for the audit trail:**

```json
{
  "imageUrls": ["https://example.com/a.jpg", "https://example.com/b.jpg"],
  "outputFormat": "jpeg",
  "maxWidth": 640,
  "maxHeight": 640,
  "includeExif": true
}
```

**3. Re-encode to AVIF for the smallest files (resized, because AVIF is memory-hungry at full size):**

```json
{
  "imageUrls": ["https://example.com/hero.jpg"],
  "outputFormat": "avif",
  "quality": 60,
  "maxWidth": 2400
}
```

#### Using your own files

The **Uploaded image files** field uses Apify's file-upload editor: it lets you upload a file to a key-value store in your account (new or existing) or paste a URL, and hands the Actor a URL. The Actor then downloads that record like any other URL (uploads are your own files, so robots.txt is not consulted for `api.apify.com` records). If you do it by hand: upload the file as a record to a key-value store in the Apify Console, copy the record URL and paste it into **Image URLs** or the upload field. Records of private stores are read with the run's own Apify token, but only when the URL you supplied is itself an `api.apify.com` key-value-store record URL; the token is never sent to any other host or on a redirect. For other private files use a signed or temporary link.

### Output

One dataset row per input image (`inputIndex` is its position in your input; rows are saved in completion order):

```json
{
  "inputIndex": 0,
  "sourceUrl": "https://upload.wikimedia.org/wikipedia/commons/a/a8/NASA-Apollo8-Dec24-Earthrise.jpg",
  "fileName": "NASA-Apollo8-Dec24-Earthrise.jpg",
  "status": "ok",
  "url": "https://api.apify.com/v2/key-value-stores/<storeId>/records/0001-NASA-Apollo8-Dec24-Earthrise.webp",
  "key": "0001-NASA-Apollo8-Dec24-Earthrise.webp",
  "inputFormat": "jpeg",
  "inputBytes": 311263,
  "width": 2400,
  "height": 2400,
  "colorSpace": "srgb",
  "hasAlpha": false,
  "hasIccProfile": false,
  "exifOrientation": null,
  "sha256": "24fd2a4f833534ca6741ed96d03b028ac1d8fda2cd129ded4d208535f541d4da",
  "perceptualHash": "0004002525c07f03",
  "outputFormat": "webp",
  "outputWidth": 800,
  "outputHeight": 800,
  "outputBytes": 16540,
  "outputSha256": "dbb40c0c83d18b7109bd31a2c33348648c561d5b5627e5595ac761008f501e33",
  "savingsPercent": 94.7,
  "metadata": "stripped",
  "charged": { "image-processed": 1, "large-image": 0 },
  "processedAt": "2026-10-20T08:27:16.290Z"
}
```

- `width` / `height` are the source dimensions **as displayed** (EXIF orientation applied); `outputWidth` / `outputHeight` are the saved image's.
- `savingsPercent` = 100 x (1 - output / input). It is **negative** when the output is bigger (for example a small JPEG converted to PNG, or an already well-compressed file re-encoded at a higher quality).
- `perceptualHash` is a 64-bit **dHash** of the source (greyscale, 9 x 8, one bit per "left is brighter than right"), 16 hex characters. Identical pictures give the same hash; resized or re-compressed copies differ by a few bits (compare with Hamming distance; up to about 8 of 64 bits usually means the same picture). It is not a face or content analysis.
- With **Return EXIF details**: `"exif": { "cameraMake": "ACME", "cameraModel": "Cam 1", "dateTime": "2024-05-17T14:03:22", "orientation": 6, "gpsRemoved": true }`.
- The key-value store also holds `RUN_SUMMARY` (counts, events charged, bytes downloaded, failures by code, peak memory, why the run stopped) and `PROGRESS` (lets a restarted run skip finished images, so nothing is processed or charged twice).

#### Status and error codes (only `ok` is charged)

| `errorCode` | Meaning |
|---|---|
| `invalid-url`, `blocked`, `dns` | Not a valid URL; not http(s); private, loopback, link-local or internal address; host does not resolve. |
| `platform` | Social-media or platform host (YouTube, TikTok, Instagram, Facebook, X, Vimeo, SoundCloud, Spotify, Twitch, Reddit, LinkedIn, Google domains). Refused before any lookup. |
| `blocked-by-robots` | The host's robots.txt disallows the path for our User-Agent. Not downloaded. |
| `slow-host` | The host's robots.txt asks for a `Crawl-delay` over 2 seconds and `allowSlowHosts` is off, or over the 3.5 s maximum this Actor honours (skipped even with `allowSlowHosts`; the delay is never shortened). Free; nothing was downloaded from the host except its robots.txt. Also reported when a redirect leads to such a host. |
| `robots-unreachable` | robots.txt could not be read (server error or timeout), so the image was not downloaded. A missing robots.txt (HTTP 404) means no restrictions. |
| `http`, `network`, `timeout`, `redirects` | The server answered with an error; connection failure; too slow or stalled; more than 5 redirects. |
| `too-large` | Above Max file size (default 25 MB, maximum 50 MB). |
| `not-an-image` | The bytes are not JPEG, PNG, WebP, GIF, AVIF or TIFF (a web page, SVG, PDF and so on). |
| `unsupported-format` | HEIC/HEIF. |
| `corrupt-image` | Damaged or truncated file. |
| `too-many-pixels` | More than 64 megapixels. |
| `avif-too-large` | AVIF output above what the run's memory can encode (about 13 megapixels at 1,024 MB). Set Max width / height, or choose WebP. |
| `budget` | The run's Max total charge cannot pay for this image (large files cost more). |
| `unexpected` | Internal error; try again, and tell us if it persists. |

### Pricing

Pay per event, plus Apify's standard Actor-start event. There is **no additional start fee** from this Actor.

| Event | Price | When |
|---|---|---|
| `image-processed` | **$0.002** | One per image saved. |
| `large-image` | **$0.001** per unit | Units = the largest of (a) started 10 MB of the **input file** above the first 5 MB: up to 5 MB = 0, up to 15 MB = 1, up to 25 MB = 2, up to 50 MB = 5 (MB = 1,048,576 bytes), (b) started 16 megapixels of **decoded pixels** above the first 16 MP: up to 16 MP = 0, up to 32 MP = 1, up to 48 MP = 2, up to 64 MP = 3, and (c) started 10 MB of the **saved output file** above the first 5 MB: an output up to 5 MB = 0, up to 15 MB = 1, up to 25 MB = 2, and so on. For animated or multi-page images only what is saved counts: when only the first frame or page is saved, only its pixels count (an animation that stays animated is at most 16 MP in total, so it adds no pixel units). The three are not added together. The output term only matters for large lossless output (full-size PNG or TIFF): a WebP, JPEG or AVIF under 5 MB, the usual result, adds nothing. The size of the output is known only after it is encoded; if your Max total charge cannot pay for the units, that image is skipped with a free `budget` row and nothing is saved. |

| Images | Typical (input up to 5 MB and 16 MP, output under 5 MB) | Each 25 MB input (2 units) | Each 64 MP photo under 5 MB (3 units) | Each 30 MB full-size PNG output (3 units) |
|---|---|---|---|---|
| 100 | $0.20 | $0.40 | $0.50 | $0.50 |
| 1,000 | $2.00 | $4.00 | $5.00 | $5.00 |
| 10,000 | $20 | $40 | $50 | $50 |

Failed, blocked, refused and skipped images cost nothing. Set **Max total charge** in the run options to cap spending: the run stops cleanly when the limit is reached and never charges for an image it did not save. Apify subscription discounts apply on top.

### Use it via API, n8n or Make

Get an API token in Apify Console → *Settings* → *API & Integrations*, and keep it in an environment variable (`APIFY_TOKEN` below). Never paste it into shared code.

**cURL** (starts a run, waits for it and returns one row per image; the synchronous endpoint waits up to 5 minutes, fine for a few dozen images; for bigger batches use the clients below):

```bash
curl -X POST "https://api.apify.com/v2/acts/ataism~image-optimizer/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"imageUrls": ["https://upload.wikimedia.org/wikipedia/commons/7/73/Pale_Blue_Dot.png"], "outputFormat": "webp", "maxWidth": 800}'
```

**JavaScript** (`npm install apify-client`):

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('ataism/image-optimizer').call({
    imageUrls: ['https://example.com/a.jpg', 'https://example.com/b.png'],
    outputFormat: 'webp',
    maxWidth: 1200,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const img of items) console.log(img.sourceUrl, img.status, img.savingsPercent, img.url);
```

**Python** (`pip install "apify-client>=3"`; with versions before 3.0, `call()` returns a dict: use `run["defaultDatasetId"]`):

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("ataism/image-optimizer").call(run_input={
    "imageUrls": ["https://example.com/a.jpg", "https://example.com/b.png"],
    "outputFormat": "webp",
    "maxWidth": 1200,
})
for img in client.dataset(run.default_dataset_id).iterate_items():
    if img["status"] != "ok":
        print("skipped (not charged):", img["sourceUrl"], img["errorCode"], img["message"])
    else:
        print(img["url"], img["outputBytes"], f'{img["savingsPercent"]}% saved', img["perceptualHash"])
```

**n8n:**

1. Start with any trigger that gives you image links (a form, a sheet row, a webhook).
2. Add the **Apify** node, operation **Run an Actor and get dataset**, Actor `ataism/image-optimizer`, input `{"imageUrls": ["{{ $json.imageUrl }}"], "maxWidth": 1200}`.
3. Use `url` (the optimized image), `perceptualHash` or `savingsPercent` in the next node (an HTTP request, a sheet, a CMS upload).

**Make:**

1. Add the **Apify** module **Run an Actor**, choose `ataism/image-optimizer`, put your image links in the input JSON and set *Run synchronously* to *Yes*.
2. Add the **Apify** module **Get Dataset Items** and map the run's default dataset ID (one bundle per image).
3. Send `url`, hashes or sizes to your next module (Google Sheets, Airtable, a webhook, a storage service).

### Privacy and terms

- **You are responsible for having the right to download, copy and process the images** you give this Actor (copyright, licences, privacy and data-protection law). It does not check this and does not find images for you. Images can show people; the Actor does no face or content analysis and returns no such data.
- **No GPS, ever.** Location data is dropped before anything is saved or returned, even when you set Keep metadata or Return EXIF details. The returned EXIF subset is camera make, model, capture date and orientation. Camera make, model and capture date can still identify a device or a moment, so treat the dataset accordingly.
- Images are processed in memory. The only files written are the optimized outputs in **your run's key-value store**, plus `RUN_SUMMARY` and `PROGRESS`. Storages belong to your Apify account and follow your retention settings; copy what you need to keep.
- The `sourceUrl` field echoes what you supplied (including a signed query string if you used one), so treat the dataset as sensitive if your URLs are. Logs show only a URL's host and path, never its query string, and never image content.
- No data is sent to any AI or third-party service. The processing is plain code (**sharp** / libvips) running inside the Actor.

### Safety

Only `http` and `https` URLs to **public** addresses are fetched. Private, loopback, link-local and internal addresses, and non-http schemes (`file:`, `ftp:` and so on), are blocked, including after redirects (at most 5, each re-checked, also against robots.txt) and at connection time. Social-media and platform hosts are refused before any DNS lookup. Files are checked by content, not by name.

### Limits and honest caveats

- **Direct image URLs only.** No page crawling, no image discovery, nothing behind a login, no CAPTCHA or block bypass: a 403, 429 or login wall is an error row.
- Up to **1,000 images per run**, **25 MB per file by default (50 MB maximum)**, **64 megapixels** per image, 4 images in parallel (fewer when memory is tight; big images wait for each other). Minimum run memory 1,024 MB; AVIF output above about 13 megapixels needs more memory (use Max width / height).
- **Crawl-delay:** a `Crawl-delay` in robots.txt is honoured in full for every download from that host (also after a redirect to it), never shortened, and waiting is not part of the download timeout. Over 2 seconds the host is "slow": its images are skipped (free `slow-host` rows) unless you set `allowSlowHosts`, because waiting between downloads makes the run long and its compute cost can exceed the image price; over 3.5 seconds they are skipped even then (free `slow-host` rows; the delay is never shortened to fit). Without a `Crawl-delay` downloads are not spaced.
- A run stops early (`stoppedBy: "unpaid-work-cap"`) after 200 failed images, 300 MB downloaded for failed images, or 10 minutes spent on failed images, so a list of bad links cannot burn compute unpaid.
- EXIF details come from the file's EXIF block (JPEG, PNG, WebP, AVIF) or, for TIFF, from the TIFF's own first directory. `gpsRemoved` is true when the source had a GPS block in either place.
- The perceptual hash is a simple dHash: good for resized and re-compressed copies, weak against crops, mirrored or heavily edited versions.
- `savingsPercent` can be negative. Re-encoding an already optimized image, or converting a lossy photo to PNG, can make it bigger.
- GIF output is the first frame unless the output stays animated (WebP or GIF). Animated images over 16 megapixels in total (frames x width x height) are saved as one frame.
- Colour: output is sRGB unless you keep the colour profile. CMYK sources are converted to sRGB.
- The public link in `url` is a key-value-store record link in your run's store. It works for as long as that store exists.

### FAQ

**Does it find images on a web page?** No. Give it the image file URLs. That keeps it simple and robots.txt-checkable.

**Why was my image not charged?** Only `ok` is charged. Check `status`, `errorCode` and `message`.

**Can I keep the EXIF?** You can keep a safe subset (colour profile, camera make, model, capture date) with *Keep metadata*, or just read it with *Return EXIF details*. GPS is always removed.

**Does the default run work without setup?** Yes. It downloads three public-domain NASA images from Wikimedia Commons and saves them as 800 px WebP.

### Open-source components

Image processing uses **sharp** (`sharp` 0.35.5, Apache-2.0) with **libvips** and its codec libraries (the `@img/sharp-libvips-*` package, LGPL-3.0-or-later, shipped as a separate dynamic library; the `@img/sharp-*` bindings are Apache-2.0), and the **Apify SDK** (`apify` 3.7.2, Apache-2.0). HEIC decoding is not included.

# Actor input Schema

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

Direct http(s) links to image files (JPEG, PNG, WebP, GIF, AVIF, TIFF), one per line. Web pages are not crawled. Private, local and internal addresses are blocked, and so are social-media and platform hosts. robots.txt of each host is checked. To use a file from your computer, upload it with the next field, or save it to a key-value store and paste its record URL here.

## `imageFiles` (type: `array`):

Upload images from your computer to a key-value store of your choice (the editor offers this), or paste a record URL. The Actor reads each file once and saves the optimized copy in this run's key-value store.

## `outputFormat` (type: `string`):

WebP and AVIF are smallest. JPEG and PNG are the most compatible. "Same as input" keeps each file's own format (GIF stays GIF, TIFF stays TIFF). Animated GIF and WebP stay animated when the output is WebP or GIF and the frames total at most 16 megapixels; otherwise only the first frame is kept.

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

Compression quality for WebP, AVIF and JPEG. 80 is a good balance. PNG is always lossless, so this does not apply to it.

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

Resize so the image is at most this wide, keeping the aspect ratio. Images are never enlarged. 0 = no limit.

## `maxHeight` (type: `integer`):

Resize so the image is at most this tall, keeping the aspect ratio ('fit inside' together with max width). Never enlarged. 0 = no limit.

## `keepMetadata` (type: `boolean`):

Off (default): all metadata is removed from the saved image. On: keeps the colour profile and these EXIF fields: camera make, model and capture date. GPS location, comments, XMP and thumbnails are always removed, even when this is on.

## `includeExif` (type: `boolean`):

Adds an "exif" object to each row with camera make, camera model, capture date and orientation. GPS location is never returned, even if you ask; the row only says whether it was present and removed ("gpsRemoved").

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

Larger files are skipped with an error and not charged. Default 25, maximum 50. Images above 64 megapixels are skipped too. Big files (above 5 MB, or above 16 megapixels) and big saved outputs (above 5 MB, for example full-size PNG) add the large-image event (see Pricing).

## `allowSlowHosts` (type: `boolean`):

Off: images on a host whose robots.txt asks for a Crawl-delay over 2 seconds are skipped (free "slow-host" rows), because waiting between downloads makes the run slow and the compute cost can exceed the image price. On: images on hosts with a delay of 2 to 3.5 seconds are downloaded and the delay is honoured in full between every request to that host, so the run takes longer and the waiting is billed as run time (a 3.5 second delay costs about 9.7% of the image price in compute at $0.20 per compute unit). Hosts that ask for more than 3.5 seconds are still skipped. Default off.

## `downloadTimeoutSecs` (type: `integer`):

Per image, including redirects. Time spent waiting for a Crawl-delay is not counted.

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

Internal: "selfTest" runs the maintainer health check and saves no items.

## Actor input object example

```json
{
  "imageUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/a/a8/NASA-Apollo8-Dec24-Earthrise.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/7/73/Pale_Blue_Dot.png",
    "https://upload.wikimedia.org/wikipedia/commons/2/2f/The_Home_Earth_%28SVS314%29.jpg"
  ],
  "outputFormat": "webp",
  "quality": 80,
  "maxWidth": 800,
  "maxHeight": 0,
  "keepMetadata": false,
  "includeExif": false,
  "maxFileSizeMb": 25,
  "allowSlowHosts": false,
  "downloadTimeoutSecs": 60,
  "mode": "run"
}
```

# Actor output Schema

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

One row per image: public link, key, sizes, savings, dimensions, hashes, optional EXIF subset, or the error (JSON, CSV, Excel or XML).

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

List of the optimized image files in the run's key-value store (each row also has a direct public link).

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

Counts, events charged, bytes downloaded, failures by code and why the run stopped.

# 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 = {
    "imageUrls": [
        "https://upload.wikimedia.org/wikipedia/commons/a/a8/NASA-Apollo8-Dec24-Earthrise.jpg",
        "https://upload.wikimedia.org/wikipedia/commons/7/73/Pale_Blue_Dot.png",
        "https://upload.wikimedia.org/wikipedia/commons/2/2f/The_Home_Earth_%28SVS314%29.jpg"
    ],
    "maxWidth": 800
};

// Run the Actor and wait for it to finish
const run = await client.actor("ataism/image-optimizer").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 = {
    "imageUrls": [
        "https://upload.wikimedia.org/wikipedia/commons/a/a8/NASA-Apollo8-Dec24-Earthrise.jpg",
        "https://upload.wikimedia.org/wikipedia/commons/7/73/Pale_Blue_Dot.png",
        "https://upload.wikimedia.org/wikipedia/commons/2/2f/The_Home_Earth_%28SVS314%29.jpg",
    ],
    "maxWidth": 800,
}

# Run the Actor and wait for it to finish
run = client.actor("ataism/image-optimizer").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 '{
  "imageUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/a/a8/NASA-Apollo8-Dec24-Earthrise.jpg",
    "https://upload.wikimedia.org/wikipedia/commons/7/73/Pale_Blue_Dot.png",
    "https://upload.wikimedia.org/wikipedia/commons/2/2f/The_Home_Earth_%28SVS314%29.jpg"
  ],
  "maxWidth": 800
}' |
apify call ataism/image-optimizer --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ataism/image-optimizer"
        }
    }
}
```

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/d176rNLTbi61A7qw9/builds/Ggny3U80RROGcyN6q/openapi.json
