# Image Compressor - Convert to WebP & AVIF, Resize, Strip GPS (`neverempty/image-compressor-webp-avif`) Actor

For product catalogs, CMS uploads and page-speed work: compress and convert images by URL to WebP, AVIF, JPEG or PNG with quality and max size. Fixes rotation, strips EXIF and GPS, returns the new file URL and % saved. A 2.7 MB phone photo came out 68.8% smaller as WebP. Failed images are free.

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

## Pricing

from $7.00 / 1,000 image converteds

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 Compressor - Convert to WebP & AVIF, Resize, Strip GPS

Give it image URLs (or keys of image files in an Apify key-value store). For each image it returns **one row with the URL of the converted file**, the original and new size in bytes, the percent saved, the original and new width and height, and the output format. The converted files are saved in a key-value store.

- **Formats:** WebP, AVIF, JPEG (MozJPEG) or PNG (palette or lossless), or keep each image's own format and just make it smaller.
- **Quality and size:** quality 1-100, maximum width and height (aspect ratio kept, never enlarged).
- **Photos come out the right way up.** Phone photos are often stored sideways with an EXIF "orientation" flag. The image is turned upright first, then the flag is removed, so it does not show sideways once the metadata is gone.
- **GPS location removed by default.** EXIF, the GPS location, XMP and IPTC are removed, and colors are converted to sRGB. Each row says whether the source carried a GPS location (`sourceHadGps`) and whether the output does (`gpsInOutput`).
- **Keep camera data but not the location.** Turn off `removeMetadata` to keep the camera EXIF and the color profile. The GPS location is still taken out of the file, and the result is read back to check it is gone. If that check fails, all metadata is removed rather than leaking the location.
- **Only new or changed images.** For catalogs and CMS jobs that run again and again: an image that has not changed since the last run with the same settings is not downloaded again (If-None-Match / If-Modified-Since) and not converted again. It comes back as a free `unchanged` row with the earlier file URL.
- **Not charged for images it could not convert.** Not an image, a broken file, too large, blocked by robots.txt, needs a sign-in, 404: each of these comes back as a free row that says why. A run where no image could be converted is not charged at all, not even the start fee.

It reads one image at a time, so a single image takes a few seconds. Measured on Apify with 1024 MB memory: an 8-megapixel phone photo (2.7 MB JPEG) to WebP, 4.6 s for the whole run, 68.8% smaller.

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `imageUrls` | array of URLs | (example photo) | Direct links to images: JPEG, PNG, WebP, AVIF, GIF, TIFF, SVG, BMP. If this and the key-value store fields are empty, one example photo from Wikimedia Commons is converted. |
| `keyValueStoreId` | key-value store | none | An Apify key-value store that holds image files you uploaded. |
| `keyValueStoreKeys` | array of keys | none | The record keys of the image files in that store. |
| `format` | `webp` / `avif` / `jpeg` / `png` / `original` | `webp` | Output format. `original` keeps each image's own format. |
| `quality` | 1-100 | WebP 80, AVIF 55, JPEG 80, PNG 80 | Lower = smaller file. Not used for lossless output or GIF. |
| `lossless` | boolean | `false` | WebP, AVIF, PNG: keep every pixel exactly. For PNG, off means palette compression. |
| `maxWidth` | px | `0` (no limit) | Shrink wider images, keeping the aspect ratio. Never enlarges. |
| `maxHeight` | px | `0` (no limit) | Shrink taller images, keeping the aspect ratio. |
| `removeMetadata` | boolean | `true` | Remove EXIF, GPS location, XMP, IPTC (after turning the photo upright). Off: keep camera EXIF and color profile, still without GPS. |
| `keepGps` | boolean | `false` | Only with `removeMetadata` off: keep the GPS location too. |
| `outputKeyValueStoreId` | key-value store | this run's store | Where to save the converted files. Choose your own store to keep the files and URLs after the run's data retention period. |
| `onlyChanged` | boolean | `false` | Skip images that have not changed since the last run with the same settings (free `unchanged` row with the earlier URL). |
| `watchName` | text | `default` | Separate memory for separate jobs when `onlyChanged` is on. |
| `resetMonitoringState` | boolean | `false` | Treat every image as new in this run. |
| `maxImageMegabytes` | 1-200 | `50` | Files larger than this are not downloaded (free `too-large` row). |
| `maxMegapixels` | 1-300 | `100` | Images with more pixels than this are not converted (free `too-large` row). |
| `requestTimeoutSecs` | 5-120 | `30` | How long to wait for one image to download. |
| `maxConcurrency` | 1-4 | `2` | Downloads at the same time. Conversion runs one image at a time. |

Example:

```json
{
  "imageUrls": ["https://upload.wikimedia.org/wikipedia/commons/f/f0/Paris_view_from_Eiffel.jpg"],
  "format": "avif",
  "quality": 50,
  "maxWidth": 1600
}
```

### Output

One dataset row per image. The file itself is in the key-value store; `outputUrl` opens it directly (a signed link, no API token needed).

```json
{
  "status": "ok",
  "source": "https://upload.wikimedia.org/wikipedia/commons/f/f0/Paris_view_from_Eiffel.jpg",
  "outputUrl": "https://api.apify.com/v2/key-value-stores/<store>/records/Paris_view_from_Eiffel-7733eeec2e4b.webp?signature=<signature>",
  "outputKey": "Paris_view_from_Eiffel-7733eeec2e4b.webp",
  "format": "webp",
  "sourceFormat": "jpeg",
  "originalBytes": 2762548,
  "outputBytes": 860782,
  "savedBytes": 1901766,
  "savedPercent": 68.8,
  "originalWidth": 3264,
  "originalHeight": 2448,
  "width": 3264,
  "height": 2448,
  "metadataRemoved": true,
  "sourceHadGps": true,
  "gpsInOutput": false,
  "colorProfile": "sRGB"
}
```

All columns: `status`, `position`, `source`, `finalUrl` (after redirects), `outputUrl`, `outputKey`, `outputKeyValueStoreId`, `format`, `sourceFormat`, `originalBytes`, `outputBytes`, `savedBytes`, `savedPercent`, `largerThanOriginal` (true when the converted file is bigger, which is reported rather than hidden), `originalWidth`, `originalHeight` (upright), `width`, `height`, `resized`, `quality`, `lossless`, `orientationFixed` (the EXIF orientation that was applied, or null), `animated`, `framesKept`, `hasAlpha`, `metadataRemoved`, `sourceHadExif`, `sourceHadGps`, `gpsInOutput`, `colorProfile`, `changeType` (`new` / `changed` / `unchanged` with `onlyChanged`), `previousConvertedAt`, `watchName`, `processingMs`, `note`, `convertedAt`.

The output key is built from the file name and a hash of the image address and the settings, so the same image with the same settings always gets the same key. A CMS can keep pointing at it.

#### Free rows (not charged)

| `status` | When |
|---|---|
| `not-an-image` | The address returned a web page or other content, not an image. |
| `broken-image` | The file looks like an image but cannot be decoded (damaged or cut short). |
| `unsupported-image` | HEIC/HEIF photos (HEVC codec) cannot be decoded here. AVIF input is fine. |
| `too-large` | Over `maxImageMegabytes` or `maxMegapixels`, a WebP over 16,383 px on a side, or a conversion that needs more memory than the run has (the row says how many MB it needs). |
| `robots-disallowed` / `robots-unreachable` | The site's robots.txt does not allow automated reading of that address, or could not be read. |
| `login-required` / `blocked` | The image needs a sign-in, or the site refused the reader or showed a check page. It does not sign in, solve check pages or switch to proxies. |
| `not-found` / `http-error` / `unreachable` / `unreadable` | 404 (or a key-value store key that does not exist), another HTTP error, no connection, or no usable answer after retries (429 and 5xx are retried). |
| `bad-input` | Not a valid http(s) URL, a private network address, or an input that cannot be used (the row says why). |
| `unchanged` | With `onlyChanged`: the image has not changed since the last run of this watch (earlier `outputUrl` included). |
| `time-limit` / `budget-reached` | The run was close to its timeout or to the maximum total charge you set; the remaining images were not started and not charged. |

### Pricing

Pay per event:

- **Image converted:** $0.01 per image on the Free plan, down to $0.007 on higher plans. Charged only when a converted file is saved and its row is returned.
- **Run start:** $0.005 per run on the Free plan, down to $0.0037. Charged once, only in a run that returns at least one converted image. With `onlyChanged` it is also charged in a run that checked images and found them unchanged, because that check is the work.

Platform usage (compute) is included; you do not pay it on top. A run whose maximum total charge has no room for the start fee plus one image requests nothing and is charged nothing.

Examples on the Free plan: 1 image per run = $0.015. 100 images in one run = $1.005.

### Memory and large images

The default memory is 1024 MB, which handles typical web and phone photos in any format, and WebP/JPEG/PNG up to about 90 megapixels. Measured on Apify at 1024 MB: a 96-megapixel, 42 MB PNG to WebP finished in 37 s (95% smaller). AVIF needs much more memory than the other formats (about 40 MB per megapixel of output). Before converting, the Actor works out how much memory the conversion needs. If it will not fit, the image is not started, and a free `too-large` row says how many MB it needs and which memory size to run with. The run does not crash and lose the other images. Setting `maxWidth` avoids most of this, because JPEG images are decoded directly at the smaller size.

### Use it from code

```bash
curl -X POST "https://api.apify.com/v2/acts/neverempty~image-compressor-webp-avif/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"imageUrls":["https://example.com/photo.jpg"],"format":"webp","maxWidth":1600}'
```

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

### Notes

- Images are fetched with a plain HTTP client from Apify's own network, following each site's robots.txt. No proxies are used to get around a refusal.
- Addresses that point to private or internal networks are not requested.
- Animated GIF/WebP keep their animation when the output is WebP; other formats keep the first frame (the row's `note` says so).
- JPEG has no transparency, so transparent areas become white (the row's `note` says so).

### Support

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

# Actor input Schema

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

Direct links to the images to compress or convert (JPEG, PNG, WebP, AVIF, GIF, TIFF, SVG, BMP). One image per line. If this and the key-value store fields are all empty, one example photo from Wikimedia Commons is converted and the log says so.

## `keyValueStoreId` (type: `string`):

An Apify key-value store that holds image files you uploaded. Use it together with the keys below, for images that are not on a public URL.

## `keyValueStoreKeys` (type: `array`):

The record keys of the image files in the key-value store above. One key per line.

## `format` (type: `string`):

webp = smallest widely supported format. avif = smaller still, slower to encode. jpeg = works everywhere (transparent areas become white). png = lossless or palette-compressed. original = keep each image's own format (JPEG, PNG, WebP, AVIF, GIF), just smaller.

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

Lower = smaller file. Empty uses a web default per format: WebP 80, AVIF 55, JPEG 80, PNG 80 (palette). Not used for lossless output or GIF.

## `lossless` (type: `boolean`):

WebP, AVIF and PNG only: keep every pixel exactly. Files are larger than lossy ones. For PNG, off means palette compression (like TinyPNG).

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

Shrink images wider than this, keeping the aspect ratio. Smaller images are never enlarged. 0 = do not resize by width.

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

Shrink images taller than this, keeping the aspect ratio. 0 = do not resize by height.

## `removeMetadata` (type: `boolean`):

On (default): the photo is first turned the right way up using its EXIF orientation, then EXIF, GPS location, XMP and IPTC are removed and colors are converted to sRGB. Off: camera EXIF and the color profile are kept, but the GPS location is still removed unless you also turn on Keep GPS location.

## `keepGps` (type: `boolean`):

Only used when Remove metadata is off. Leave off unless you really want the place where the photo was taken to stay in the file.

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

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

## `onlyChanged` (type: `boolean`):

For repeated runs over the same images (a catalog, a CMS): an image that has not changed since the last run with the same settings is not downloaded again (If-None-Match / If-Modified-Since) or converted again. It comes back as a free unchanged row with the earlier file URL. A run that checked images this way pays the run start fee once.

## `watchName` (type: `string`):

Keeps separate memories for separate jobs when Only convert new or changed images is on. Letters, digits, dot, dash and underscore, up to 40.

## `resetMonitoringState` (type: `boolean`):

Treat every image as new in this run (it is converted and remembered again).

## `maxImageMegabytes` (type: `integer`):

An image file larger than this is not downloaded or converted and comes back as a free too-large row.

## `maxMegapixels` (type: `integer`):

An image with more pixels than this (width x height) is not converted and comes back as a free too-large row. A conversion that needs more memory than the run has is not started either; its free row says how many MB it needs.

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

How long to wait for one image to download.

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

How many images to download at the same time. Conversion itself runs one image at a time.

## Actor input object example

```json
{
  "imageUrls": [
    "https://upload.wikimedia.org/wikipedia/commons/f/f0/Paris_view_from_Eiffel.jpg"
  ],
  "format": "webp",
  "lossless": false,
  "maxWidth": 0,
  "maxHeight": 0,
  "removeMetadata": true,
  "keepGps": false,
  "onlyChanged": false,
  "resetMonitoringState": false,
  "maxImageMegabytes": 50,
  "maxMegapixels": 100,
  "requestTimeoutSecs": 30,
  "maxConcurrency": 2
}
```

# Actor output Schema

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

One row per image: the URL of the converted file, its key and key-value store, output format, original and new size in bytes, bytes and percent saved, original and new width and height, whether the photo was rotated, whether the source carried EXIF and a GPS location and whether the output does, and the color profile handling. An image that could not be read, is not an image, is too large, is blocked by robots.txt or needs a sign-in comes back as a free row that says why.

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

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

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

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

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

```

## MCP server setup

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

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/kwiNEc0GFiqITtFbE/builds/7n3ierIk4mnduQAhf/openapi.json
