The Iminify API lets your own code do everything the website does. Send an image and get back a smaller one, in the format and at the size you asked for. Give it the address of a web page and it finds every image the page loads and compresses them all. Rename, share or re-run what you made, and download one file or a zip of many.
It's JSON over HTTPS. Every request goes to https://www.iminify.com/api/v1, carries your API key, and gets JSON back, apart from downloads, which get the file.
Quickstart
You need an account with a verified email address. The free plan works; there is no separate API plan and nothing to pay for.
1. Create a key
Open Settings > API Tokens, give the key a name you'll recognise later (the script or the server it's for), and press Create. Copy the key straight away: it is shown once. Then put it in your environment, where every sample on these pages reads it from:
export IMINIFY_API_KEY="paste-your-key-here"
2. Check it works
curl "https://www.iminify.com/api/v1/account" \
-H "Authorization: Bearer $IMINIFY_API_KEY" \
-H "Accept: application/json"
A 200 with your plan in it means the key is good. A 401 means it was mistyped, and a 403 with email_not_verified means the account still has a verification email waiting.
3. Compress an image
This script uploads photo.jpg, asks for WebP at 1600 pixels wide, waits for the result and saves it. Pick your language; the choice sticks on every page.
#!/usr/bin/env bash
# Compress photo.jpg to WebP at 1600 pixels wide, wait for it, save the result.
# Needs curl 7.76 or newer and jq.
set -euo pipefail
API="https://www.iminify.com/api/v1"
AUTH="Authorization: Bearer $IMINIFY_API_KEY"
# 1. Upload. The answer comes back straight away, queued, with the image's id.
id=$(curl -sS --fail-with-body "$API/images" \
-H "$AUTH" -H "Accept: application/json" \
-F "[email protected]" -F "format=webp" -F "width=1600" \
| jq -r '.data.id')
# 2. Poll until it has finished.
while true; do
image=$(curl -sS --fail-with-body "$API/images/$id" -H "$AUTH" -H "Accept: application/json")
status=$(jq -r '.data.status' <<< "$image")
case "$status" in
completed|already-optimized) break ;;
failed|cancelled) echo "The image ended as $status" >&2; exit 1 ;;
esac
sleep 2
done
jq -r '.data | "\(.original.size) bytes -> \(.optimized.size) bytes (\(.saved_percent)% smaller)"' <<< "$image"
# 3. Download the optimized copy under the name it was given.
curl -sS --fail-with-body -o "$(jq -r '.data.name' <<< "$image")" "$API/images/$id/download" -H "$AUTH"
"""Compress photo.jpg to WebP at 1600 pixels wide, wait for it, save the result.
Needs Python 3.8 or newer and the requests package (pip install requests).
"""
import os
import time
import requests
API = "https://www.iminify.com/api/v1"
session = requests.Session()
session.headers.update({
"Authorization": f"Bearer {os.environ['IMINIFY_API_KEY']}",
"Accept": "application/json",
})
# 1. Upload. The answer comes back straight away, queued, with the image's id.
with open("photo.jpg", "rb") as file:
response = session.post(f"{API}/images", files={"file": file}, data={"format": "webp", "width": "1600"})
response.raise_for_status()
image = response.json()["data"]
# 2. Poll until it has finished.
while image["status"] in ("queued", "optimizing"):
time.sleep(2)
response = session.get(f"{API}/images/{image['id']}")
response.raise_for_status()
image = response.json()["data"]
if image["status"] not in ("completed", "already-optimized"):
raise SystemExit(f"The image ended as {image['status']}")
print(f"{image['original']['size']} bytes -> {image['optimized']['size']} bytes ({image['saved_percent']}% smaller)")
# 3. Download the optimized copy under the name it was given.
response = session.get(image["optimized"]["download_url"])
response.raise_for_status()
with open(image["name"], "wb") as out:
out.write(response.content)
<?php
declare(strict_types=1);
// Compress photo.jpg to WebP at 1600 pixels wide, wait for it, save the result.
// Needs PHP 8.1 or newer and Guzzle (composer require guzzlehttp/guzzle).
require __DIR__ . '/vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client([
'base_uri' => 'https://www.iminify.com/api/v1/',
'headers' => [
'Authorization' => 'Bearer ' . getenv('IMINIFY_API_KEY'),
'Accept' => 'application/json',
],
]);
// 1. Upload. The answer comes back straight away, queued, with the image's id.
$response = $client->post('images', [
'multipart' => [
['name' => 'file', 'contents' => fopen('photo.jpg', 'r'), 'filename' => 'photo.jpg'],
['name' => 'format', 'contents' => 'webp'],
['name' => 'width', 'contents' => '1600'],
],
]);
$image = json_decode((string) $response->getBody(), true)['data'];
// 2. Poll until it has finished.
while (in_array($image['status'], ['queued', 'optimizing'], true)) {
sleep(2);
$response = $client->get("images/{$image['id']}");
$image = json_decode((string) $response->getBody(), true)['data'];
}
if ( ! in_array($image['status'], ['completed', 'already-optimized'], true)) {
fwrite(STDERR, "The image ended as {$image['status']}\n");
exit(1);
}
echo "{$image['original']['size']} bytes -> {$image['optimized']['size']} bytes ({$image['saved_percent']}% smaller)\n";
// 3. Download the optimized copy under the name it was given.
$client->get("images/{$image['id']}/download", ['sink' => $image['name']]);
// Compress photo.jpg to WebP at 1600 pixels wide, wait for it, save the result.
// Needs Node 20 or newer. Save it as compress.mjs and run: node compress.mjs
import { openAsBlob } from 'node:fs';
import { writeFile } from 'node:fs/promises';
import { setTimeout as sleep } from 'node:timers/promises';
const API = 'https://www.iminify.com/api/v1';
const call = async (path, options = {}) => {
const response = await fetch(`${API}${path}`, {
...options,
headers: {
Authorization: `Bearer ${process.env.IMINIFY_API_KEY}`,
Accept: 'application/json',
},
});
if (!response.ok) {
throw new Error(`${response.status}: ${(await response.json()).message}`);
}
return response;
};
// 1. Upload. The answer comes back straight away, queued, with the image's id.
const form = new FormData();
form.append('file', await openAsBlob('photo.jpg'), 'photo.jpg');
form.append('format', 'webp');
form.append('width', '1600');
let { data: image } = await (await call('/images', { method: 'POST', body: form })).json();
// 2. Poll until it has finished.
while (['queued', 'optimizing'].includes(image.status)) {
await sleep(2000);
({ data: image } = await (await call(`/images/${image.id}`)).json());
}
if (!['completed', 'already-optimized'].includes(image.status)) {
throw new Error(`The image ended as ${image.status}`);
}
console.log(`${image.original.size} bytes -> ${image.optimized.size} bytes (${image.saved_percent}% smaller)`);
// 3. Download the optimized copy under the name it was given.
const download = await call(`/images/${image.id}/download`);
await writeFile(image.name, Buffer.from(await download.arrayBuffer()));
That's the whole shape of the API: create something, poll it until it has finished, then fetch the result.
How it works
Work happens in the background. An upload answers straight away with 202 Accepted, the status queued and a Location header pointing at the image. A worker usually picks it up within seconds, the status moves to optimizing, and it ends as completed, already-optimized or failed, or as cancelled if someone calls it off while it waits (from the API or the website). Most images take a few seconds; a large photograph converted to AVIF can take a minute. Poll the image every second or two until the status changes. Page scans work the same way, over minutes rather than seconds.
It's your account, not a copy of it. Images you compress through the API show up in the results table on the website, and the other way round. Both spend the same allowances: an image compressed on the website counts against the same daily and per-minute limits as one sent through the API. Your plan's limits are on the pricing page.
Nothing is deleted behind your back. An image and its optimized copy stay until you delete them, from the API or the website.
What you can do
- Compress an image from a file or from its address, at one of four levels.
- Convert it to WebP, AVIF, PNG or JPG, or let
autotry each one and keep the smallest. - Resize it to a width, a height, both, or a percentage, and keep or strip its EXIF metadata.
- Download the optimized copy or the original, or several at once as a zip.
- Compress it again with other settings, retry a failure, or cancel one that hasn't started.
- Rename one image, or a batch with numbers in upload order.
- Share a result on a public page, and switch the link off again.
- Scan a page for every image it loads, and download them all as one zip.
- Read your plan, limits and usage.
Conventions
- Every request carries
Authorization: Bearer <key>; Authentication has the details. - Send
Accept: application/json. Errors come back as JSON either way, in one shape. - Bodies are JSON, except an upload, which is
multipart/form-data. - Ids are UUIDs. Sizes are in bytes, dimensions in pixels, and times in ISO 8601, in UTC.
- Lists come a page at a time, 15 to a page unless you ask for up to 100 with
per_page, withlinksandmetato find the rest.
For tools and agents
The whole API is described in an OpenAPI 3.1 document at https://www.iminify.com/api/v1/openapi.json, with every sample on these pages inside it. Import it into Postman, Insomnia or a client generator, or hand it to an AI agent that should call the API for you. It needs no key.