# Image Transform — WebP / JPEG / PNG Batch Converter (`marielise.dev/image-converter`) Actor

Shrink images to WebP, JPEG, or PNG with lossy or lossless encoding, web-ready resize, EXIF stripping. Drop in URLs, upload via KVS, base64, or pipe a scraper's dataset. Photos go ~85% smaller, pages load faster. Tiered pricing $0.002–$0.060/image, $0.005 typical. Sharp-powered, no subscription.

- **URL**: https://apify.com/marielise.dev/image-converter.md
- **Developed by:** [Marielise](https://apify.com/marielise.dev) (community)
- **Categories:** Developer tools, Automation, E-commerce
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, NaN bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 image transformed (xs — up to 500 kb)s

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## Image Transform — Batch WebP / JPEG / PNG Converter

**Drop in image URLs (or chain a scraper's dataset). Get back compressed, web-ready files in WebP, JPEG, or PNG. Powered by sharp. Tiered PPE pricing from $0.002 (XS) to $0.060 (XXL) per image — you only pay for what your images actually weigh.**

> 💸 **Tiered pricing by source size: XS $0.002 → XXL $0.060.** A typical phone photo (M tier, 2-10 MB) is **$0.005**. Logos/icons (XS) are **$0.002**. Failures, broken URLs, HEIC, and oversize (>100 MB) images are NEVER charged. See full table in [Pricing](#pricing).

### What it does

- Accepts images from **public URLs**, **uploaded files** (Apify KVS), **inline base64**, or an **upstream dataset** — all four can be mixed in one run
- Re-encodes to **WebP** (recommended), **JPEG** (universal), or **PNG** (lossless)
- Web-ready resize with aspect-preserving cap (`inside`, `cover`, or `contain` fit)
- Strips EXIF / ICC / XMP metadata for smaller, privacy-safe output (EXIF orientation honored first)
- Returns a metadata row per image to your dataset, plus the encoded file in the run's key-value store

### Best for

- **E-commerce image prep** — bulk-convert product photos to WebP at 80-quality, capped at 2000px
- **Blog / CMS optimization** — strip a folder of giant 4000px CMS uploads into 1600px WebP without touching originals
- **Post-scrape pipelines** — pipe a scraper's image-URL dataset directly into this actor (no glue code)
- **Asset migration** — convert legacy JPEG / PNG archives to modern formats with predictable bytes-saved metrics
- **Archival masters** — produce lossless WebP from PNG to shave 20-40% off storage with zero quality loss

### How to feed it images

Pick whichever source you have. **All four can be combined in a single run** — they merge into one queue and process together.

#### 1. Public URL (easiest, works with anything online)

Paste any direct image link. Examples that all work as-is:

| Service | URL pattern |
|---|---|
| Your own CDN / S3 public | `https://cdn.example.com/photo.jpg` |
| Cloudflare R2 (public bucket) | `https://pub-xxxxx.r2.dev/photo.jpg` |
| AWS S3 (public) | `https://bucket.s3.amazonaws.com/photo.jpg` |
| Google Drive ("Anyone with link") | `https://drive.google.com/uc?id=FILE_ID&export=download` ([how to extract `FILE_ID`](https://drive.google.com/file/d/THIS_BIT/view)) |
| Dropbox shared link | take the share link and replace `?dl=0` with `?dl=1` |
| Imgur direct | `https://i.imgur.com/xxxxxx.jpg` |
| GitHub raw | `https://raw.githubusercontent.com/user/repo/main/img.png` |

→ Set `imageUrls` to a list of `{ "url": "..." }` entries.

#### 2. Upload local files via Apify KVS (no public URL needed)

For images you can't or don't want to host publicly:

1. In Apify Console, open any **Key-Value Store** (Storage → Key-value stores → "Create new" or use the run's input store).
2. Drag your files onto the store. Each file becomes a record with the filename as the key.
3. Note the store's **ID or name**, and the **list of keys** (filenames you uploaded).
4. In this actor's input:
   - `inputKvsId`: the store ID (leave blank to use the run's default INPUT store)
   - `inputKvsKeys`: `["photo1.jpg", "photo2.jpg", "logo.png"]`

The actor reads each key as binary and feeds it through the same pipeline.

#### 3. Inline base64 (tiny tests + API integrations)

For one-off tests or programmatic API calls where the image is already in memory:

```json
{
  "base64Images": [
    { "name": "screenshot.png", "base64": "iVBORw0KGgo..." }
  ]
}
````

`name` controls the output filename. `data:image/...;base64,` prefixes are stripped automatically. Don't use this for batches above ~10 images — JSON gets huge.

#### 4. Chain from another actor (dataset)

Already running a scraper that emits image URLs? Point at its dataset:

- `inputDatasetId`: the upstream dataset ID
- `urlField`: which field holds the URL (default `url`)

The actor will pull every item, read the URL field, and queue it. Great for "scrape product → optimize images" pipelines.

#### What this actor cannot accept

- ❌ Local file paths from your machine (the actor runs on Apify cloud — no access to your disk). Upload via KVS instead.
- ❌ HEIC files (Linux can't decode Apple's HEVC codec — pre-convert to JPEG first using macOS `sips` or any HEIC→JPEG tool).
- ❌ OAuth-gated cloud storage (Drive private folders, Dropbox without share link). Use a public link instead.

### Input fields

All fields are optional (sane defaults). Provide images via at least one of: `imageUrls`, `inputKvsKeys`, `base64Images`, or `inputDatasetId`.

| Field | Type | Default | Description |
|---|---|---|---|
| `imageUrls` | `Array<{url}>` | — | Public image URLs (any service: R2, S3, Drive direct, Dropbox `?dl=1`, etc.). JPEG / PNG / WebP / GIF / TIFF / AVIF supported. HEIC is skipped. |
| `inputKvsId` | string | run's default | Apify KVS holding uploaded source files. Leave blank to use the run's INPUT store. |
| `inputKvsKeys` | string\[] | — | Keys (filenames) to read from `inputKvsId`. |
| `base64Images` | `Array<{name,base64}>` | — | Inline base64 images for tiny tests / API calls. |
| `inputDatasetId` | string | — | Pull URLs from an existing Apify dataset. Use to chain after a scraper. |
| `urlField` | string | `url` | Which field on each dataset item holds the image URL. |
| `format` | `webp` | `jpeg` | `png` | `webp` | Output format. PNG is always lossless. JPEG always lossy. WebP honors `mode`. |
| `mode` | `lossy` | `lossless` | `lossy` | WebP only. Ignored for JPEG / PNG. |
| `quality` | 1–100 | `82` | Encoder quality (JPEG always; WebP lossy only; PNG ignored). 75–85 is the sweet spot for web photos. |
| `maxWidth` | int | `2000` | Cap output width. `0` = no limit. Aspect ratio preserved (no upscaling). |
| `maxHeight` | int | `2000` | Cap output height. `0` = no limit. |
| `fit` | `inside` | `cover` | `contain` | `inside` | How the image fits the box. `inside` = preserve aspect, no crop (recommended). |
| `stripMetadata` | bool | `true` | Drop EXIF / ICC / XMP for smaller, privacy-safe files. Orientation honored first. |
| `effort` | 0–9 | `4` | Encoder effort. Higher = smaller files, slower encode. |
| `outputKvsKeyTemplate` | string | `{slug}.{format}` | Filename template. Tokens: `{slug}` `{format}` `{index}` `{originalName}`. |
| `slugifyFilenames` | bool | `true` | Slug-clean source filenames before substitution. |
| `concurrency` | 0–32 | `0` (auto) | Parallel images. `0` matches CPU count. sharp is CPU-bound. |
| `useApifyProxy` | bool | `false` | Route source-URL fetches through Apify Proxy datacenter group. |

#### Tokens for `outputKvsKeyTemplate`

| Token | Example value | Notes |
|---|---|---|
| `{slug}` | `photo_1015` | Slugified source filename. NFD-normalized, diacritic-free, `[^A-Za-z0-9._-]` replaced with `_`. |
| `{originalName}` | `Photo 1015 (final)` | Raw source filename, no slugification. Useful only if your source URLs are already file-safe. |
| `{format}` | `webp` | The output format extension. |
| `{index}` | `001` | Zero-padded position in the input list. Width matches input count. |

### Output

#### Per-image dataset record

```json
{
  "kvsKey": "photo_1015.webp",
  "kvsUrl": "https://api.apify.com/v2/key-value-stores/<id>/records/photo_1015.webp",
  "format": "webp",
  "mode": "lossy",
  "width": 2000,
  "height": 1333,
  "bytes": 184273,
  "originalBytes": 1842739,
  "originalFormat": "jpeg",
  "originalWidth": 4000,
  "originalHeight": 2666,
  "compressionRatio": 0.1,
  "compressionPct": 10,
  "sourceUrl": "https://picsum.photos/id/1015/4000/2666.jpg",
  "tier": "image-s",
  "chargedUsd": 0.003,
  "error": null,
  "skipped": false
}
```

#### Encoded files

Each successful transform writes the encoded buffer to the run's default **key-value store**. Download via `kvsUrl` (one click from the dataset table view) or programmatically through the Apify API. KVS retention follows your account's default policy (named stores never expire).

#### Failures

Failed rows still appear in the dataset, with `error` populated and `skipped: false`. HEIC inputs appear with `error: "HEIC not supported on Linux"` and `skipped: true`. Neither pattern is charged.

### Lossy vs lossless

> ⚠️ **Default is `lossy` (WebP at quality 82) — best for photos.** Switch to `lossless` if you're converting **logos, icons, screenshots, line art, or UI assets** — lossy compression makes hard edges fuzzy and can put visible ringing around text. For pure photographs, lossy is essentially indistinguishable from the source and 5–10× smaller. When in doubt: photos = lossy, anything with crisp edges = lossless. PNG is forced lossless regardless of `mode`, so picking `format: png` is a one-flag way to be safe.

| Goal | Pick | Why |
|---|---|---|
| Smallest possible files for the web (photos) | `webp` + `lossy` + `quality: 78–85` | 5–10× smaller than source JPEG, near-imperceptible loss |
| Pixel-perfect compression for masters | `webp` + `lossless` | 20–40% smaller than PNG, zero loss |
| Universal compatibility (old browsers, email) | `jpeg` + `quality: 82` | Always lossy, decoded everywhere |
| Logos, icons, screenshots, transparency, line art | `png` | Always lossless, supports alpha |
| UI assets / brand assets you'll re-edit later | `webp` + `lossless` OR `png` | Re-encoding lossy compounds quality loss; lossless is stable forever |

#### Format compatibility matrix

| Format | Lossy supported? | Lossless supported? | Transparency? |
|---|---|---|---|
| WebP | ✅ (default) | ✅ (set `mode: "lossless"`) | ✅ |
| JPEG | ✅ (always — `mode` ignored) | ❌ | ❌ |
| PNG | ❌ | ✅ (always — `mode` ignored) | ✅ |

### Examples

#### 1. Bulk web optimization (default)

```json
{
  "imageUrls": [
    { "url": "https://example.com/hero.jpg" },
    { "url": "https://example.com/banner.png" }
  ],
  "format": "webp",
  "mode": "lossy",
  "quality": 82,
  "maxWidth": 2000,
  "maxHeight": 2000,
  "fit": "inside",
  "stripMetadata": true
}
```

Two source images → two WebPs in your KVS, capped at 2000px, no metadata, ~85% smaller on average.

#### 2. Archival masters (lossless WebP)

```json
{
  "imageUrls": [{ "url": "https://example.com/master.png" }],
  "format": "webp",
  "mode": "lossless",
  "maxWidth": 0,
  "maxHeight": 0,
  "stripMetadata": false,
  "outputKvsKeyTemplate": "{originalName}.{format}"
}
```

No resize, no metadata stripping, lossless WebP. Typical 25-35% smaller than the source PNG.

#### 3. Format conversion to JPEG

```json
{
  "imageUrls": [{ "url": "https://example.com/photo.png" }],
  "format": "jpeg",
  "quality": 90,
  "maxWidth": 2400,
  "fit": "inside"
}
```

PNG → JPEG, no transparency (PNG alpha is composited onto white).

#### 4. Chain after a scraper (dataset input)

```json
{
  "inputDatasetId": "<dataset-id-from-upstream-scraper>",
  "urlField": "imageUrl",
  "format": "webp",
  "quality": 80,
  "maxWidth": 1600,
  "concurrency": 8,
  "useApifyProxy": true
}
```

Reads `imageUrl` from each item in the upstream dataset, fetches via Apify Proxy, transforms, and pushes a metadata row + KVS file per image. Drop this directly into your scraping schedule.

### HEIC limitation

HEIC / HEIF inputs are **not supported on Linux** (sharp does not ship libheif support in its prebuilt linux-x64 binaries — adding it would require a custom build chain that breaks on the Apify base image).

When the actor sees a HEIC URL or `image/heic` content type, it pushes a row with `error: "HEIC not supported on Linux"` and `skipped: true`, and **does not charge** the PPE event.

**Workaround:** pre-convert HEIC sources to JPEG before submission. Local options:

- macOS: `sips -s format jpeg input.heic --out output.jpg`
- Linux: `heif-convert input.heic output.jpg` (requires `libheif-examples`)
- Web: any HEIC→JPEG service / Cloud Convert API

Then submit the converted JPEGs to this actor.

### Pricing

**Tiered Pay-Per-Event by source byte size.** You pay only for successfully transformed images, priced according to how heavy the *original* file was — small images cost less, big images cost more.

| Tier | Source size | Price per image | What it covers | Apify event |
|---|---|---:|---|---|
| **XS** | ≤ 500 KB | **$0.002** | Icons, favicons, simple logos, thumbnails | `image-xs` |
| **S** | 500 KB – 2 MB | **$0.003** | Web thumbnails, screenshots, small photos | `image-s` |
| **M** | 2 – 10 MB | **$0.005** | Phone photos, product shots, web hero images (default workhorse) | `image-m` |
| **L** | 10 – 25 MB | **$0.012** | DSLR JPEG, drone photos, large uploads | `image-l` |
| **XL** | 25 – 50 MB | **$0.025** | High-res photography, archive jobs | `image-xl` |
| **XXL** | 50 – 100 MB | **$0.060** | Huge originals near platform limit | `image-xxl` |
| reject | > 100 MB | — | Returns error in dataset row, NOT charged. Pre-resize and retry. | — |

The dataset row for each successful image includes the `tier` and `chargedUsd` fields so you can audit exactly what each image cost.

#### Quick cost estimates

| Run | Mix | You pay |
|---|---|---:|
| 100 phone photos (3 MB each) | all M | $0.50 |
| 1,000 phone photos | all M | $5.00 |
| 1,000 e-commerce thumbnails (200 KB) | all XS | $2.00 |
| 1,000 logo conversions (50 KB) | all XS | $2.00 |
| 100 DSLR photos (15 MB) | all L | $1.20 |
| Mixed: 700 M + 200 S + 100 XS | typical blog | $4.30 |
| 80,000 archive migration (~3 MB avg) | mostly M | ~$400 |

#### What is NOT charged

- Failed fetches (404, timeout, broken host) → free
- Decode errors (corrupt files, unsupported format) → free
- HEIC inputs (skipped row) → free
- Oversize inputs > 100 MB (rejected with error) → free
- Empty / aborted runs → free

You only pay for images that successfully reach your key-value store.

#### Why tiered?

Encode cost scales with image size. A 100 MB scan needs **30× more compute** than a 500 KB logo, and uses **20× more memory**. Flat pricing would either rip off small-image users or lose money on huge ones. Tiered pricing keeps it fair: pay-for-what-you-use.

There is no subscription, no minimum, no per-run fee. Mixed-size batches are charged per-image at the appropriate tier — the run summary log breaks down exactly what was charged at which tier.

### FAQ

**Why WebP by default?**
Best size/quality trade-off for the modern web. Supported by every browser shipped after ~2020, ~25–35% smaller than equivalent JPEG, ~20–40% smaller than equivalent PNG. If you need universal compatibility (email clients, ancient browsers), pick JPEG.

**Can I keep the originals?**
Yes — the actor only fetches; it never writes back to the source URL. Your originals are untouched. The transformed copies live in this run's key-value store, downloadable individually or via the Apify API.

**Does it strip my watermark?**
No. Pixel content is preserved; only the EXIF / ICC / XMP **metadata blocks** are stripped (when `stripMetadata: true`). A watermark burned into the pixels stays.

**How big can input files be?**
**100 MB hard ceiling.** Files above 100 MB are rejected with a clear error in the dataset row (and NOT charged) to protect your run from out-of-memory crashes that could fail in-flight images alongside the giant one. The actor's memory is configured 1024–8192 MB, which comfortably handles XXL inputs up to the 100 MB cap. For larger inputs (gigapixel maps, archive TIFFs), pre-resize locally before submitting.

**Is concurrency tuned per memory tier?**
Default `concurrency: 0` matches CPU count, which Apify scales 1 core per 4 GB. So at 4096 MB you get 1 core → concurrency 1; at 16384 MB you get 4 cores → concurrency 4. Override manually if you need more throughput on a smaller tier.

**Does it handle animated GIF or animated WebP?**
The first frame only. sharp's animation support is incomplete; multi-frame round-tripping is out of scope for this actor. If you need full animation preservation, run sharp directly with `animated: true` in custom code.

**Does it support AVIF?**
AVIF is supported as **input** (decoded transparently). AVIF as **output** is intentionally not exposed yet — encode times are 5–20× WebP for marginal size wins, which doesn't fit this actor's price point. If demand picks up we'll add it as an opt-in format.

### Changelog

#### v1.0.0 — Launch

- WebP, JPEG, PNG output
- Lossy / lossless mode for WebP
- Web-ready resize with `inside` / `cover` / `contain` fit
- EXIF / ICC / XMP metadata stripping
- Four input sources: direct URLs, Apify KVS uploads, inline base64, upstream dataset
- Apify Proxy support for source fetches
- Configurable concurrency (auto-matches CPU count)
- 6-tier PPE pricing by source byte size (XS $0.002 → XXL $0.060)
- 100 MB hard ceiling — oversize sources rejected without charge
- HEIC inputs gracefully skipped (not charged)

# Actor input Schema

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

Public image URLs to fetch and transform. Works with any direct image link: your CDN, Cloudflare R2 / S3 public buckets, Google Drive (use `https://drive.google.com/uc?id=FILE_ID&export=download`), Dropbox (replace `?dl=0` with `?dl=1`), Imgur, etc. JPEG / PNG / WebP / GIF / TIFF / AVIF supported. HEIC is skipped (Linux can't decode Apple's HEVC codec — see README).

## `inputKvsId` (type: `string`):

Apify Key-Value Store ID or name holding source images. Leave blank to use the run's default INPUT store. Workflow: open a KVS in Apify Console → drag files in → list the keys below. This is the way to feed local files into the actor (no public URL needed).

## `inputKvsKeys` (type: `array`):

Keys of files inside the KVS specified above (or the default). One key per entry. Each key points to a binary image record uploaded to the store.

## `base64Images` (type: `array`):

Inline images for tiny tests / API integrations. Each entry: `{ "name": "photo.jpg", "base64": "..." }`. Don't use this for large batches — use URLs or KVS uploads instead.

## `inputDatasetId` (type: `string`):

Pull image URLs from an existing Apify dataset. The actor reads the field named in `urlField` from each item. Use this to chain image-transform after a scraper actor.

## `urlField` (type: `string`):

Which field on each input dataset item holds the image URL. Only used when `inputDatasetId` is set.

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

Encoded output format. WebP gives the best size/quality tradeoff for the modern web. JPEG is universal. PNG is lossless and supports transparency.

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

Default `lossy` is best for PHOTOS. Switch to `lossless` for logos, icons, screenshots, or UI assets — lossy makes hard edges fuzzy and adds ringing around text. WebP only — ignored for JPEG (always lossy) and PNG (always lossless).

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

Encoder quality. Used for JPEG always, and for WebP in lossy mode. Ignored for PNG. 75-85 is the sweet spot for web photos.

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

Cap output width. 0 = no limit. Aspect ratio is preserved unless you change `fit`. Images smaller than this are NOT upscaled.

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

Cap output height. 0 = no limit. Aspect ratio is preserved unless you change `fit`. Images smaller than this are NOT upscaled.

## `fit` (type: `string`):

How the image fits the max box. `inside` preserves aspect ratio without cropping (recommended). `cover` fills the box and crops overflow. `contain` fits inside and pads to box size.

## `stripMetadata` (type: `boolean`):

Drop EXIF / ICC / XMP metadata for smaller, privacy-safe web output. EXIF orientation is honored before stripping (so portrait photos stay portrait).

## `effort` (type: `integer`):

Higher = smaller files, slower encode. WebP uses 0-6, PNG uses 0-9 (compressionLevel), JPEG ignores this.

## `outputKvsKeyTemplate` (type: `string`):

Controls how transformed files are named in the run's key-value store. Tokens: `{slug}` (slugified source filename), `{format}` (output extension), `{index}` (zero-padded counter), `{originalName}` (raw source filename, no slugification). Default produces e.g. `photo_1015.webp`.

## `slugifyFilenames` (type: `boolean`):

Strip diacritics, spaces, and special characters from `{slug}` to produce URL/file-safe keys. Recommended on. Disable only if you need the original filename verbatim (use `{originalName}` instead).

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

How many images to process in parallel. sharp is CPU-bound, so the sweet spot is roughly the number of CPU cores. Default 0 = auto (matches CPU count).

## `useApifyProxy` (type: `boolean`):

Route the URL fetch step through Apify Proxy (datacenter group). Recommended for high-volume runs so you don't hammer source CDNs from a single IP. Has no effect when `inputDatasetId` is the only source.

## Actor input object example

```json
{
  "imageUrls": [
    {
      "url": "https://picsum.photos/id/1015/4000/2666.jpg"
    },
    {
      "url": "https://picsum.photos/id/1025/3000/2000.jpg"
    }
  ],
  "inputKvsKeys": [],
  "base64Images": [],
  "urlField": "url",
  "format": "webp",
  "mode": "lossy",
  "quality": 82,
  "maxWidth": 2000,
  "maxHeight": 2000,
  "fit": "inside",
  "stripMetadata": true,
  "effort": 4,
  "outputKvsKeyTemplate": "{slug}.{format}",
  "slugifyFilenames": true,
  "concurrency": 0,
  "useApifyProxy": false
}
```

# Actor output Schema

## `transformedImages` (type: `string`):

One record per image: KVS key, KVS download URL, output format/mode/dims/bytes, original format/dims/bytes, compression ratio, source URL.

## `keyValueStore` (type: `string`):

The encoded image buffers, one entry per successfully transformed image. Use these URLs to download the converted files directly.

# 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": [
        {
            "url": "https://picsum.photos/id/1015/4000/2666.jpg"
        },
        {
            "url": "https://picsum.photos/id/1025/3000/2000.jpg"
        }
    ],
    "inputKvsKeys": [],
    "base64Images": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("marielise.dev/image-converter").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": [
        { "url": "https://picsum.photos/id/1015/4000/2666.jpg" },
        { "url": "https://picsum.photos/id/1025/3000/2000.jpg" },
    ],
    "inputKvsKeys": [],
    "base64Images": [],
}

# Run the Actor and wait for it to finish
run = client.actor("marielise.dev/image-converter").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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": [
    {
      "url": "https://picsum.photos/id/1015/4000/2666.jpg"
    },
    {
      "url": "https://picsum.photos/id/1025/3000/2000.jpg"
    }
  ],
  "inputKvsKeys": [],
  "base64Images": []
}' |
apify call marielise.dev/image-converter --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=marielise.dev/image-converter",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Image Transform — WebP / JPEG / PNG Batch Converter",
        "description": "Shrink images to WebP, JPEG, or PNG with lossy or lossless encoding, web-ready resize, EXIF stripping. Drop in URLs, upload via KVS, base64, or pipe a scraper's dataset. Photos go ~85% smaller, pages load faster. Tiered pricing $0.002–$0.060/image, $0.005 typical. Sharp-powered, no subscription.",
        "version": "0.0",
        "x-build-id": "oXweulh5Z4LSp032R"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/marielise.dev~image-converter/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-marielise.dev-image-converter",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/marielise.dev~image-converter/runs": {
            "post": {
                "operationId": "runs-sync-marielise.dev-image-converter",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/marielise.dev~image-converter/run-sync": {
            "post": {
                "operationId": "run-sync-marielise.dev-image-converter",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "imageUrls": {
                        "title": "Image URLs (any public link)",
                        "type": "array",
                        "description": "Public image URLs to fetch and transform. Works with any direct image link: your CDN, Cloudflare R2 / S3 public buckets, Google Drive (use `https://drive.google.com/uc?id=FILE_ID&export=download`), Dropbox (replace `?dl=0` with `?dl=1`), Imgur, etc. JPEG / PNG / WebP / GIF / TIFF / AVIF supported. HEIC is skipped (Linux can't decode Apple's HEVC codec — see README).",
                        "items": {
                            "type": "object",
                            "required": [
                                "url"
                            ],
                            "properties": {
                                "url": {
                                    "type": "string",
                                    "title": "URL of a web page",
                                    "format": "uri"
                                }
                            }
                        }
                    },
                    "inputKvsId": {
                        "title": "Input KVS ID (for uploaded files)",
                        "type": "string",
                        "description": "Apify Key-Value Store ID or name holding source images. Leave blank to use the run's default INPUT store. Workflow: open a KVS in Apify Console → drag files in → list the keys below. This is the way to feed local files into the actor (no public URL needed)."
                    },
                    "inputKvsKeys": {
                        "title": "KVS keys",
                        "type": "array",
                        "description": "Keys of files inside the KVS specified above (or the default). One key per entry. Each key points to a binary image record uploaded to the store.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "base64Images": {
                        "title": "Inline base64 images",
                        "type": "array",
                        "description": "Inline images for tiny tests / API integrations. Each entry: `{ \"name\": \"photo.jpg\", \"base64\": \"...\" }`. Don't use this for large batches — use URLs or KVS uploads instead."
                    },
                    "inputDatasetId": {
                        "title": "Input dataset ID (chain from another actor)",
                        "type": "string",
                        "description": "Pull image URLs from an existing Apify dataset. The actor reads the field named in `urlField` from each item. Use this to chain image-transform after a scraper actor."
                    },
                    "urlField": {
                        "title": "URL field name",
                        "type": "string",
                        "description": "Which field on each input dataset item holds the image URL. Only used when `inputDatasetId` is set.",
                        "default": "url"
                    },
                    "format": {
                        "title": "Output format",
                        "enum": [
                            "webp",
                            "jpeg",
                            "png"
                        ],
                        "type": "string",
                        "description": "Encoded output format. WebP gives the best size/quality tradeoff for the modern web. JPEG is universal. PNG is lossless and supports transparency.",
                        "default": "webp"
                    },
                    "mode": {
                        "title": "Encoding mode",
                        "enum": [
                            "lossy",
                            "lossless"
                        ],
                        "type": "string",
                        "description": "Default `lossy` is best for PHOTOS. Switch to `lossless` for logos, icons, screenshots, or UI assets — lossy makes hard edges fuzzy and adds ringing around text. WebP only — ignored for JPEG (always lossy) and PNG (always lossless).",
                        "default": "lossy"
                    },
                    "quality": {
                        "title": "Quality (1-100)",
                        "minimum": 1,
                        "maximum": 100,
                        "type": "integer",
                        "description": "Encoder quality. Used for JPEG always, and for WebP in lossy mode. Ignored for PNG. 75-85 is the sweet spot for web photos.",
                        "default": 82
                    },
                    "maxWidth": {
                        "title": "Max width (px)",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Cap output width. 0 = no limit. Aspect ratio is preserved unless you change `fit`. Images smaller than this are NOT upscaled.",
                        "default": 2000
                    },
                    "maxHeight": {
                        "title": "Max height (px)",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Cap output height. 0 = no limit. Aspect ratio is preserved unless you change `fit`. Images smaller than this are NOT upscaled.",
                        "default": 2000
                    },
                    "fit": {
                        "title": "Resize fit",
                        "enum": [
                            "inside",
                            "cover",
                            "contain"
                        ],
                        "type": "string",
                        "description": "How the image fits the max box. `inside` preserves aspect ratio without cropping (recommended). `cover` fills the box and crops overflow. `contain` fits inside and pads to box size.",
                        "default": "inside"
                    },
                    "stripMetadata": {
                        "title": "Strip metadata",
                        "type": "boolean",
                        "description": "Drop EXIF / ICC / XMP metadata for smaller, privacy-safe web output. EXIF orientation is honored before stripping (so portrait photos stay portrait).",
                        "default": true
                    },
                    "effort": {
                        "title": "Encoder effort (0-9)",
                        "minimum": 0,
                        "maximum": 9,
                        "type": "integer",
                        "description": "Higher = smaller files, slower encode. WebP uses 0-6, PNG uses 0-9 (compressionLevel), JPEG ignores this.",
                        "default": 4
                    },
                    "outputKvsKeyTemplate": {
                        "title": "Output KVS key template",
                        "type": "string",
                        "description": "Controls how transformed files are named in the run's key-value store. Tokens: `{slug}` (slugified source filename), `{format}` (output extension), `{index}` (zero-padded counter), `{originalName}` (raw source filename, no slugification). Default produces e.g. `photo_1015.webp`.",
                        "default": "{slug}.{format}"
                    },
                    "slugifyFilenames": {
                        "title": "Slugify filenames",
                        "type": "boolean",
                        "description": "Strip diacritics, spaces, and special characters from `{slug}` to produce URL/file-safe keys. Recommended on. Disable only if you need the original filename verbatim (use `{originalName}` instead).",
                        "default": true
                    },
                    "concurrency": {
                        "title": "Concurrency",
                        "minimum": 0,
                        "maximum": 32,
                        "type": "integer",
                        "description": "How many images to process in parallel. sharp is CPU-bound, so the sweet spot is roughly the number of CPU cores. Default 0 = auto (matches CPU count).",
                        "default": 0
                    },
                    "useApifyProxy": {
                        "title": "Use Apify Proxy for source fetches",
                        "type": "boolean",
                        "description": "Route the URL fetch step through Apify Proxy (datacenter group). Recommended for high-volume runs so you don't hammer source CDNs from a single IP. Has no effect when `inputDatasetId` is the only source.",
                        "default": false
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
