Bulk Image Downloader & Optimizer: Resize, WebP, EXIF
Pricing
from $2.00 / 1,000 image processeds
Bulk Image Downloader & Optimizer: Resize, WebP, EXIF
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.
Pricing
from $2.00 / 1,000 image processeds
Rating
0.0
(0)
Developer
Kaizoku Data
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
2 days ago
Last modified
Categories
Share
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):
- Checks the host's
robots.txtfor the image path (our User-Agent isimage-optimizer-actor/0.1 (https://apify.com/ataism/image-optimizer)). A disallowed path is not downloaded and gets the errorblocked-by-robots, uncharged. ACrawl-delayin 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 (freeslow-hostrows) unless you setallowSlowHosts, over 3.5 seconds always. - 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.
- Reads dimensions, format, colour space, transparency and EXIF orientation; computes SHA-256 and the perceptual hash of the source.
- Rotates upright (EXIF orientation), optionally resizes, converts, and removes metadata.
- 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:
{"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:
{"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):
{"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):
{"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/heightare the source dimensions as displayed (EXIF orientation applied);outputWidth/outputHeightare 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).perceptualHashis 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) andPROGRESS(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):
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):
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"]):
import osfrom apify_client import ApifyClientclient = 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:
- Start with any trigger that gives you image links (a form, a sheet row, a webhook).
- Add the Apify node, operation Run an Actor and get dataset, Actor
ataism/image-optimizer, input{"imageUrls": ["{{ $json.imageUrl }}"], "maxWidth": 1200}. - Use
url(the optimized image),perceptualHashorsavingsPercentin the next node (an HTTP request, a sheet, a CMS upload).
Make:
- 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. - Add the Apify module Get Dataset Items and map the run's default dataset ID (one bundle per image).
- 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_SUMMARYandPROGRESS. Storages belong to your Apify account and follow your retention settings; copy what you need to keep. - The
sourceUrlfield 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-delayin 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 (freeslow-hostrows) unless you setallowSlowHosts, 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 (freeslow-hostrows; the delay is never shortened to fit). Without aCrawl-delaydownloads 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.
gpsRemovedis 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.
savingsPercentcan 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
urlis 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.