Reference

Page scans API

Scan a web page for every image it loads, compress them all, and download them as one zip. Every page scans endpoint with samples.

A page scan opens a web page in a real browser on Iminify's servers, waits for it to load, scrolls to the bottom so lazy images load too, and collects every image the page loaded along the way. Each one is then downloaded and compressed with the settings you gave the scan, as an image of its own on your account, and the scan reports what's worth fixing in how the page shows them.

It's the same scanner as the website's. A scan counts one against your daily page scans; the images it finds don't count against your daily images.

To scan several pages of one site at once, on a paid plan, start a site crawl: each of its pages is a page scan, named by the crawl's id in crawl_id. The scan list leaves a crawl's pages out, because the crawl lists them, and a page is deleted with its crawl, never on its own.

The life of a scan

TEXT
queued ──► scanning ──► completed ──► (its images compress)
   │                ├──► no-image-found
   │                └──► failed
   └──► cancelled

The scanner itself takes from a few seconds to a few minutes, depending on the page. When it's done the scan is completed and its images are queued, and they keep compressing after that. The scan is fully done once its status has ended and images_in_progress is 0. note says what the scanner found and what it did with each image, or why it failed.

A scan passes over SVG and AVIF files, images larger than your plan's file size, tiny tracking pixels, and the same image found twice. A page of a weekly site check also passes over an image whose file an earlier check compressed, as unchanged. Each one it passed over is listed in skipped with its address, a reason code and a message, so you can tell which file it was. It stops at your plan's images per scan; the images past that were never fetched, so note counts them but skipped doesn't list them. A page on a private network is refused, and a page behind a login usually comes back with no images, because the scanner sees what a signed-out visitor sees.

The page report

A scan also reports how the page shows its images. The same browser measures them at a desktop width of 1366 pixels, before anything has scrolled. Get a single scan and the report is in audit (the ScanAudit object); on the website, it opens from the scan's Report button. It covers every image address the page had, including the ones past your plan's images per scan: the browser measured those, even though the scan didn't download them. audit.images lists up to 1000 addresses, and images_found counts them all.

Each entry in audit.images has the image's format and bytes as a browser received them, its own size against the size it's shown at, whether it's in the first screen, its alt text, and a list of findings:

Finding What it means The fix
oversized The file is more than twice as wide and twice as tall as the space it's shown in. Twice is what a high density screen draws, so an image sized for those isn't flagged. Only images an img element shows are sized; a CSS background isn't. Resize it to twice its shown size.
without_dimensions The image has no width and height attributes and no CSS aspect ratio, so the page moves when it arrives. Images placed with position: absolute or fixed don't count. Add both attributes.
lazy_above_fold loading="lazy" on an image in the first screen, which delays it. Remove the attribute.
eager_below_fold An image below the first screen that loads straight away. An image a script loads as you scroll doesn't count. Add loading="lazy".
missing_alt The image has no alt text. alt="", which marks an image as decorative, is fine. Describe the image (suggested_alt has a start), or use alt="".
lcp_not_prioritized The image is the page's largest contentful paint (largest_paint) and lacks fetchpriority="high". Add the attribute.

wasted_bytes on an oversized image, and oversized_bytes for the whole page, are estimates. They take the share of the image's pixels that would go at twice its shown size and apply it to the image's bytes. Bytes don't shrink exactly with pixels, so read them as "about".

largest_paint names the page's largest contentful paint: an image (with its address), text, or none. A browser asks for WebP and AVIF and the scan's own download doesn't, so a site can send the two different files: format and bytes are the browser's, and the compressed image (image_id) has its own sizes. When the scan's format is auto, format_sizes shows what the compressed file came to in each format it tried.

For an image with missing_alt, the scan writes an AI alt text from its compressed copy and the report carries it in suggested_alt. They're written one at a time after the images finish, so the scan is done for the report once alt_texts_in_progress is 0 as well. Images marked decorative, or that already have alt text, get none, and nothing renames a scan's images. An image whose bytes the scan already compressed under an address that has alt text shares that copy and gets none; ask for one on its image_id and the report shows it. Send alt_text as false with the scan to leave it out, or alt_text_language for another language; each one counts against your daily AI alt texts.

outcome says what the scan did with each address: compressed, skipped (with a skip_reason), or left_out when it never got to it. An address that redirected is listed once, under the address the page asked for, with the format and bytes of the final answer. If the browser can't open a page, the scan reads its HTML instead; then rendered is false and only the addresses are known. audit is null until the scan has finished, for a scan that failed, and for scans made before the report existed.

Sharing and exporting the report

On a paid plan, a finished scan's report can be handed to someone without an account, three ways. Other plans are answered with pro_required, and a scan without a report with report_not_available.

  • A public link. POST /scans/{id}/share gives the report a page at an address nobody can guess and returns the scan with it in share_url. Anyone with the link can read the report, with thumbnails of the compressed images; nothing on the page can be downloaded or changed, and search engines are asked not to index it. Sharing again keeps the same link, and DELETE /scans/{id}/share switches it off. The link answers only while your account is on a paid plan: it pauses if the plan ends and comes back on renewal, and you can switch it off on any plan.
  • A CSV. GET /scans/{id}/report.csv has one row per image address, with the fields audit.images has, findings separated by spaces, the bytes of the file the scan downloaded, and the compressed copy's format and bytes. A page that could only be read as HTML leaves its flags empty. A cell that would start with =, +, - or @ starts with an apostrophe, so a spreadsheet never runs it as a formula.
  • A PDF. GET /scans/{id}/report.pdf prints the report for a client: the findings with their fixes, the page's largest paint and formats, then every image to fix. The CSV lists every image. Printing takes a second or two, and one PDF prints at a time on your account; a second request meanwhile is answered with rate_limited.

Both files are made for the request that asks for them and are never stored. On the website, the three are buttons in the scan's report; through MCP they are share_scan_report and export_scan_report.

A whole page in one script

Scan a page, wait for the scanner and every image, then download them all as one zip:

#!/usr/bin/env bash
# Scan a page, wait for the scanner and every image it found, save them as one zip.
# Needs curl 7.76 or newer and jq.
# Each call gives up after 10 seconds without a connection and after --max-time seconds overall,
# so a stalled network can't hang the script.
set -euo pipefail

API="https://www.iminify.com/api/v1"
AUTH="Authorization: Bearer $IMINIFY_API_KEY"

# 1. Queue the scan with the settings every image gets.
id=$(curl -sS --fail-with-body --connect-timeout 10 --max-time 30 "$API/scans" \
  -H "$AUTH" -H "Accept: application/json" -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/pricing", "format": "webp"}' \
  | jq -r '.data.id')

# 2. Wait for the scanner, then for the images it queued.
while true; do
  scan=$(curl -sS --fail-with-body --connect-timeout 10 --max-time 30 "$API/scans/$id" \
    -H "$AUTH" -H "Accept: application/json")
  status=$(jq -r '.data.status' <<< "$scan")
  pending=$(jq -r '.data.images_in_progress' <<< "$scan")

  case "$status" in
    queued|scanning) ;;
    completed) [ "$pending" -eq 0 ] && break ;;
    *) jq -r '.data.note' <<< "$scan" >&2; exit 1 ;;
  esac

  sleep 5
done

jq -r '.data | "\(.images_finished) images, \(.saved_percent)% smaller"' <<< "$scan"

# 3. Download every finished image as one zip.
curl -sS --fail-with-body --connect-timeout 10 --max-time 1800 -o scan.zip "$API/scans/$id/download" -H "$AUTH"

What the zip holds

A scan's zip is laid out like the page. Each image sits in the folders of its address, so https://example.com/wp-content/uploads/2024/hero.jpg comes back as wp-content/uploads/2024/hero.jpg. An image from another host, a CDN say, sits under a folder named after that host (cdn.example.net/...). The query string is dropped.

An image keeps its file name while its format stays the same, so it can replace the original as it is. A converted image keeps the name and takes its new extension (hero.webp), and a renamed one takes the name you gave it. Two images that land on the same path get (2) before the extension of the second.

manifest.csv at the root has a row for every image address the scan fetched: the address, the file in the zip, the status, both formats and both sizes. Addresses it passed over are listed with their reason and message, and a duplicate points at the file it shares with the first copy. Images past your plan's images per scan were never fetched, so a last row only counts them. Each image's address is also its source_url. Images from scans made before addresses were kept have none, so they sit at the root.

If you compress one of the scan's images again, both results go in the zip. The newer one takes the page's file name and the older one gets the (2).

The Scan object

id string
The scan's id.
url string
The page that was scanned.
crawl_id string or null
The site crawl this scan is a page of, or null for a page scanned on its own. A crawl's pages are left out of the scan list, because the crawl lists them, and are deleted with their crawl.
status string
Where the scanner is. queued: waiting for the scanner. scanning: the page is open and its images are being collected. completed: images were found and queued. no-image-found: the page loaded but nothing on it could be compressed. failed: the page could not be scanned. cancelled: called off before it started. The images themselves keep compressing after completed; watch images_in_progress.
note string or null
What the scanner wrote when it finished: how many images it found and what became of each, or why it failed. null until then.
settings object
The settings every image the scan found is compressed with.
settings.level string
The compression level.
settings.format string or null
The format every image is converted to, or null to keep each one's own.
settings.keep_metadata boolean
Whether EXIF metadata is kept.
settings.width integer or null
The width every image is resized to, or null.
settings.height integer or null
The height every image is resized to, or null.
settings.scale integer or null
The percentage every image is scaled to, or null.
skipped array
Every image the scan found and did not compress, with why: duplicate (the same bytes as another address on the page, which was tried instead, or on a page of a site crawl an image another page of the crawl compressed first, which message names), unsupported_format (an SVG, say), too_large, tracking_pixel, private_network, unreadable, unreachable (the address failed to answer, or answered with an error), refused (it failed the checks every upload goes through) or failed. message says the same in a sentence. Images past your plan's per-scan limit or the scan's time budget were never fetched, so they are not listed; note counts them. Empty until the scan finishes.
images_count integer
How many images the scan queued.
images_in_progress integer
How many of them are still queued or being compressed. The scan is done once its status has ended and this is 0.
alt_texts_in_progress integer
How many finished images still have an AI alt text being written for the report's suggested_alt. They are written one at a time, so this can take a minute after images_in_progress reaches 0.
images_finished integer
How many of them have finished.
original_size integer
Bytes of the finished images before compression.
optimized_size integer
Bytes of the same images after compression.
saved_bytes integer
The difference between the two.
saved_percent number or null
The same saving as a percentage, or null while no image has finished.
download_url string
Where to download every finished image as one zip.
images array
The scan's images, newest first. Only on a single scan, not in a list.
audit string or null
The page audit: how the page shows its images (oversized files, missing dimensions, lazy loading in the wrong place, missing alt text, the largest contentful paint), measured in a browser for every image the page had, including the ones past your plan's images per scan. null until the scan has finished, for a scan that failed, and for one made before the audit existed. Only on a single scan, not in a list.
share_url string or null
The page report's public link while it is shared, or null. The link answers only while your account is on a paid plan.
created_at timestamp
When the scan was queued.
updated_at timestamp
When it last changed.

The ScanAudit object

rendered boolean
Whether a browser opened the page. false when only its HTML could be read: then the image addresses are known and nothing about how they are shown, so every finding count is 0.
images_found integer
How many image addresses the page had. images lists them all unless a page had more than 1000.
largest_paint object or null
The page's largest contentful paint (LCP): the biggest thing in the first screen, which is when a visitor sees the page as loaded. null when it could not be measured.
largest_paint.kind string
largest_paint.element string or null
largest_paint.url string or null
findings object
How many images have each finding.
findings.oversized integer
Images whose file is more than twice as wide and twice as tall as the space they are shown in.
findings.without_dimensions integer
Images shown without width and height attributes (and no CSS aspect ratio), so the page can move as they load.
findings.lazy_above_fold integer
Images in the first screen that are lazy loaded, which delays them.
findings.eager_below_fold integer
Images below the first screen that are not lazy loaded, so they load before they are needed.
findings.missing_alt integer
Images without alt text. An empty alt="", which marks an image as decorative, is not counted.
findings.lcp_not_prioritized integer
1 when the largest paint is an image without fetchpriority="high", which would have the browser fetch it first.
oversized_bytes integer
About how many bytes the oversized images would shed if each were served at twice the size it is shown, the size a high density screen draws. An estimate from the share of pixels that would go.
bytes integer
The bytes a browser downloaded for the images listed.
formats array
How many images arrived in each format, the commonest first, as a browser received them: a site can send a browser WebP or AVIF and the scan's own download the original format.
images array
Every image address the page had, in the page's order.

The ScanAuditImage object

url string
The image's address on the page.
format string or null
The format a browser received it in (jpg, png, webp, avif, gif, svg, ...), or null when it did not load as an image.
bytes integer or null
The bytes a browser downloaded for it, or null when it did not load as an image.
natural_width integer or null
The file's own width in pixels, or null when it is not shown by an img element or did not load.
natural_height integer or null
The file's own height in pixels.
rendered_width integer or null
The width it is shown at on a desktop screen 1366 pixels wide, the largest where the page shows it more than once. null when no img element shows it (a CSS background, a hidden image).
rendered_height integer or null
The height it is shown at.
above_fold boolean or null
Whether it is in the first screen before scrolling, or null when that is not known.
largest_paint boolean
Whether it is the page's largest contentful paint.
alt string or null
Whether it has alt text: present, empty (alt="", or marked as decorative) or missing. null when no img element shows it.
findings array
What is worth fixing about it, empty when nothing.
wasted_bytes integer or null
About how many bytes it would shed at twice the size it is shown, when it is oversized; otherwise null.
outcome string
What the scan did with it. compressed: it is one of the scan's images (image_id). skipped: passed over, with skip_reason; a duplicate names the image it shares its bytes with in image_id, which on a page of a site crawl can be an image another page of the crawl compressed. left_out: never compressed, because the scan stopped at your plan's images per scan or its time budget, or the image has been deleted since.
skip_reason string or null
Why it was skipped, the same codes as the scan's skipped list, or null.
image_id string or null
The id of its compressed image, or null.
format_sizes array or null
What the compressed file came to in each format, smallest first, when the scan's format was auto and more than one format was tried; otherwise null.
suggested_alt string or null
An AI alt text for an image the page shows without one, written from its compressed copy, or null: the image has alt text, the scan was asked for none, it is still being written (the scan's alt_texts_in_progress counts it), the day's AI alt texts were used up, or the model gave no usable answer. See Alt text and file names.

List page scans

GET /api/v1/scans

Every page scan on your account, newest first, each with its counts and totals. The pages of a site crawl are left out: the crawl lists them. The images themselves come with a single scan, or from the image list filtered by scan.

Query parameters

status
string
Only scans with this status.

queued scanning completed failed no-image-found cancelled

per_page
integer
Scans a page, 1 to 100. 1 to 100 Default: 15
page
integer
The page to read. 1 to 10000000 Default: 1

Request

curl "https://www.iminify.com/api/v1/scans?status=completed" \
  --connect-timeout 10 --max-time 30 \
  -H "Authorization: Bearer $IMINIFY_API_KEY" \
  -H "Accept: application/json"

Response 200

JSON
{
    "data": [
        {
            "id": "9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7",
            "url": "https://example.com/pricing",
            "crawl_id": null,
            "status": "completed",
            "note": "Rendered in a headless browser. 14 image URLs found: 12 queued for compression, 1 duplicates of an image already queued, 1 in a format the compressor does not accept.",
            "settings": {
                "level": "smart",
                "format": "webp",
                "keep_metadata": false,
                "width": 1600,
                "height": null,
                "scale": null
            },
            "skipped": [
                {
                    "url": "https://cdn.example.com/pricing/hero.jpg?v=2",
                    "reason": "duplicate",
                    "message": "The same image as https://example.com/images/hero.jpg, so it was compressed once."
                },
                {
                    "url": "https://example.com/images/logo.svg",
                    "reason": "unsupported_format",
                    "message": "The file is SVG, a format Iminify does not compress."
                }
            ],
            "images_count": 12,
            "images_in_progress": 0,
            "alt_texts_in_progress": 0,
            "images_finished": 12,
            "original_size": 18406331,
            "optimized_size": 2114902,
            "saved_bytes": 16291429,
            "saved_percent": 88.51,
            "download_url": "https://www.iminify.com/api/v1/scans/9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7/download",
            "share_url": null,
            "created_at": "2026-10-01T11:05:00+00:00",
            "updated_at": "2026-10-01T11:05:41+00:00"
        }
    ],
    "links": {
        "first": "https://www.iminify.com/api/v1/scans?page=1",
        "last": "https://www.iminify.com/api/v1/scans?page=1",
        "prev": null,
        "next": null
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "links": [
            {
                "url": null,
                "label": "&laquo; Previous",
                "page": null,
                "active": false
            },
            {
                "url": "https://www.iminify.com/api/v1/scans?page=1",
                "label": "1",
                "page": 1,
                "active": true
            },
            {
                "url": null,
                "label": "Next &raquo;",
                "page": null,
                "active": false
            }
        ],
        "path": "https://www.iminify.com/api/v1/scans",
        "per_page": 15,
        "to": 1,
        "total": 1
    }
}

Scan a page

POST /api/v1/scans

Opens the page in a browser on Iminify's servers, waits for it to load, scrolls to the bottom and collects every image it loaded, then compresses each one with the settings you send. It answers straight away with the status queued; a scan takes from a few seconds to a few minutes. Each scan counts one against your daily scans and your page scans a minute, and the images it finds don't count against your daily images. A scan stops at your plan's images per scan, skips SVG and AVIF files and anything over your plan's file size, and compresses an image that appears twice once.

The address can leave out https://. A page on a private network is refused. A page behind a login, or one that blocks automated browsers, usually comes back with no images, because the scanner sees what a signed-out visitor would.

Body (JSON)

url
string (URL) required
The page to scan. https:// can be left out.
level
string
How hard to compress. smart finds the lowest quality that still looks like your upload, ultra goes further and softens fine texture a little, lossless changes no pixel unless a size_limit needs it to, none only applies the format, the resize, the size limit and the metadata choice. See Compression settings.

none lossless ultra smart

Default: smart
format
string
Convert to this format. auto encodes the image in every format that can hold it and keeps the smallest. Leave it out to keep the upload's own format; a HEIC or TIFF always comes back in another one, because Iminify doesn't write either. jpeg is read as jpg.

auto webp avif png jpg

keep_metadata
boolean
Keep the EXIF metadata (camera, date, location) in the optimized copy. Off by default, which strips it; the orientation is applied to the pixels first, so a photo never comes back on its side. Default: false
width
integer
Resize to this width in pixels. Alone, the height follows the aspect ratio. With height too, the image is resized to exactly that size, and its proportions change if they differ. Up to 65535. 1 to 65535
height
integer
Resize to this height in pixels, the same way as width. Up to 65535. 1 to 65535
scale
integer
Resize to this percentage of the original, 1 to 100. Cannot be combined with width or height. 1 to 100
alt_text
boolean
Write an AI alt text for each image the page shows with no alt attribute at all, from its compressed copy, for the report's suggested_alt. On by default; send false to leave it out. Images marked decorative (alt="") or that already have alt text get none. Each one counts against your daily AI alt texts. See Alt text and file names. Default: true
alt_text_language
string
The language to write the alt text in, as one of these tags: en (English), es (Spanish), fr (French), de (German), it (Italian), pt (Portuguese), nl (Dutch), pl (Polish), sv (Swedish), tr (Turkish), ru (Russian), uk (Ukrainian), ar (Arabic), fa (Persian), he (Hebrew), hi (Hindi), id (Indonesian), vi (Vietnamese), th (Thai), ja (Japanese), ko (Korean), zh-Hans (Simplified Chinese), zh-Hant (Traditional Chinese). The suggested file name follows it when the language is written in the Latin alphabet, and is in English otherwise.

en es fr de it pt nl pl sv tr ru uk ar fa he hi id vi th ja ko zh-Hans zh-Hant

Default: en

Request

curl -X POST "https://www.iminify.com/api/v1/scans" \
  --connect-timeout 10 --max-time 30 \
  -H "Authorization: Bearer $IMINIFY_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/pricing","level":"smart","format":"webp"}'

Response 202

JSON
{
    "data": {
        "id": "9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7",
        "url": "https://example.com/pricing",
        "crawl_id": null,
        "status": "queued",
        "note": null,
        "settings": {
            "level": "smart",
            "format": "webp",
            "keep_metadata": false,
            "width": 1600,
            "height": null,
            "scale": null
        },
        "skipped": [],
        "images_count": 0,
        "images_in_progress": 0,
        "alt_texts_in_progress": 0,
        "images_finished": 0,
        "original_size": 0,
        "optimized_size": 0,
        "saved_bytes": 0,
        "saved_percent": null,
        "download_url": "https://www.iminify.com/api/v1/scans/9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7/download",
        "share_url": null,
        "created_at": "2026-10-01T11:05:00+00:00",
        "updated_at": "2026-10-01T11:05:00+00:00"
    }
}

Get a page scan

GET /api/v1/scans/{id}

One scan with every image it found, newest first, and its page audit. Poll this while a scan runs: it is done once its status is no longer queued or scanning and images_in_progress is 0, and the report's suggested_alt texts once alt_texts_in_progress is 0 too.

Path parameters

id
string (id) required
The scan's id.

Request

curl "https://www.iminify.com/api/v1/scans/9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7" \
  --connect-timeout 10 --max-time 30 \
  -H "Authorization: Bearer $IMINIFY_API_KEY" \
  -H "Accept: application/json"

Response 200

JSON
{
    "data": {
        "id": "9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7",
        "url": "https://example.com/pricing",
        "crawl_id": null,
        "status": "completed",
        "note": "Rendered in a headless browser. 14 image URLs found: 12 queued for compression, 1 duplicates of an image already queued, 1 in a format the compressor does not accept.",
        "settings": {
            "level": "smart",
            "format": "webp",
            "keep_metadata": false,
            "width": 1600,
            "height": null,
            "scale": null
        },
        "skipped": [
            {
                "url": "https://cdn.example.com/pricing/hero.jpg?v=2",
                "reason": "duplicate",
                "message": "The same image as https://example.com/images/hero.jpg, so it was compressed once."
            },
            {
                "url": "https://example.com/images/logo.svg",
                "reason": "unsupported_format",
                "message": "The file is SVG, a format Iminify does not compress."
            }
        ],
        "images_count": 12,
        "images_in_progress": 0,
        "alt_texts_in_progress": 0,
        "images_finished": 12,
        "original_size": 18406331,
        "optimized_size": 2114902,
        "saved_bytes": 16291429,
        "saved_percent": 88.51,
        "download_url": "https://www.iminify.com/api/v1/scans/9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7/download",
        "images": [
            {
                "id": "9d3c5b8e-6f0a-4c1e-9b7d-2a4e8f1c6b35",
                "status": "completed",
                "name": "team-offsite.webp",
                "original": {
                    "name": "team-offsite.jpg",
                    "format": "jpg",
                    "size": 2841205,
                    "width": 4032,
                    "height": 3024,
                    "download_url": "https://www.iminify.com/api/v1/images/9d3c5b8e-6f0a-4c1e-9b7d-2a4e8f1c6b35/download?type=original"
                },
                "optimized": {
                    "name": "team-offsite.webp",
                    "format": "webp",
                    "size": 212877,
                    "width": 1600,
                    "height": 1200,
                    "download_url": "https://www.iminify.com/api/v1/images/9d3c5b8e-6f0a-4c1e-9b7d-2a4e8f1c6b35/download"
                },
                "saved_bytes": 2628328,
                "saved_percent": 92.51,
                "settings": {
                    "level": "smart",
                    "format": "webp",
                    "keep_metadata": false,
                    "width": 1600,
                    "height": null,
                    "scale": null,
                    "size_limit": null,
                    "responsive_widths": null
                },
                "size_limit_status": null,
                "content_credentials": {
                    "status": "removed",
                    "generative_ai": false
                },
                "alt_text": null,
                "responsive": null,
                "scan_id": "9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7",
                "source_url": "https://example.com/images/team-offsite.jpg",
                "folder": null,
                "share_url": null,
                "created_at": "2026-10-01T09:30:00+00:00",
                "updated_at": "2026-10-01T09:30:09+00:00"
            },
            {
                "id": "9d3c5b91-0b2e-4a7f-8c1d-5e6f7a8b9c0d",
                "status": "completed",
                "name": "hero-banner.webp",
                "original": {
                    "name": "hero-banner.jpg",
                    "format": "jpg",
                    "size": 2841205,
                    "width": 4032,
                    "height": 3024,
                    "download_url": "https://www.iminify.com/api/v1/images/9d3c5b91-0b2e-4a7f-8c1d-5e6f7a8b9c0d/download?type=original"
                },
                "optimized": {
                    "name": "hero-banner.webp",
                    "format": "webp",
                    "size": 212877,
                    "width": 1600,
                    "height": 1200,
                    "download_url": "https://www.iminify.com/api/v1/images/9d3c5b91-0b2e-4a7f-8c1d-5e6f7a8b9c0d/download"
                },
                "saved_bytes": 2628328,
                "saved_percent": 92.51,
                "settings": {
                    "level": "smart",
                    "format": "webp",
                    "keep_metadata": false,
                    "width": 1600,
                    "height": null,
                    "scale": null,
                    "size_limit": null,
                    "responsive_widths": null
                },
                "size_limit_status": null,
                "content_credentials": {
                    "status": "removed",
                    "generative_ai": false
                },
                "alt_text": null,
                "responsive": null,
                "scan_id": "9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7",
                "source_url": "https://example.com/images/hero-banner.jpg",
                "folder": null,
                "share_url": null,
                "created_at": "2026-10-01T09:30:00+00:00",
                "updated_at": "2026-10-01T09:30:09+00:00"
            }
        ],
        "audit": {
            "rendered": true,
            "images_found": 14,
            "largest_paint": {
                "kind": "image",
                "element": "img",
                "url": "https://example.com/images/hero-banner.jpg"
            },
            "findings": {
                "oversized": 1,
                "without_dimensions": 1,
                "lazy_above_fold": 0,
                "eager_below_fold": 1,
                "missing_alt": 1,
                "lcp_not_prioritized": 1
            },
            "oversized_bytes": 180384,
            "bytes": 899380,
            "formats": [
                {
                    "format": "webp",
                    "count": 2
                },
                {
                    "format": "jpg",
                    "count": 1
                },
                {
                    "format": "svg",
                    "count": 1
                }
            ],
            "images": [
                {
                    "url": "https://example.com/images/hero-banner.jpg",
                    "format": "webp",
                    "bytes": 412306,
                    "natural_width": 3200,
                    "natural_height": 1800,
                    "rendered_width": 1200,
                    "rendered_height": 675,
                    "above_fold": true,
                    "largest_paint": true,
                    "alt": "present",
                    "findings": [
                        "oversized",
                        "lcp_not_prioritized"
                    ],
                    "wasted_bytes": 180384,
                    "outcome": "compressed",
                    "skip_reason": null,
                    "image_id": "9d3c5b91-0b2e-4a7f-8c1d-5e6f7a8b9c0d",
                    "format_sizes": null,
                    "suggested_alt": null
                },
                {
                    "url": "https://example.com/images/team-offsite.jpg",
                    "format": "webp",
                    "bytes": 96118,
                    "natural_width": 1600,
                    "natural_height": 1200,
                    "rendered_width": 800,
                    "rendered_height": 600,
                    "above_fold": false,
                    "largest_paint": false,
                    "alt": "missing",
                    "findings": [
                        "without_dimensions",
                        "eager_below_fold",
                        "missing_alt"
                    ],
                    "wasted_bytes": null,
                    "outcome": "compressed",
                    "skip_reason": null,
                    "image_id": "9d3c5b8e-6f0a-4c1e-9b7d-2a4e8f1c6b35",
                    "format_sizes": null,
                    "suggested_alt": null
                },
                {
                    "url": "https://cdn.example.com/pricing/hero.jpg?v=2",
                    "format": "jpg",
                    "bytes": 388042,
                    "natural_width": null,
                    "natural_height": null,
                    "rendered_width": null,
                    "rendered_height": null,
                    "above_fold": null,
                    "largest_paint": false,
                    "alt": null,
                    "findings": [],
                    "wasted_bytes": null,
                    "outcome": "skipped",
                    "skip_reason": "duplicate",
                    "image_id": null,
                    "format_sizes": null,
                    "suggested_alt": null
                },
                {
                    "url": "https://example.com/images/logo.svg",
                    "format": "svg",
                    "bytes": 2914,
                    "natural_width": 160,
                    "natural_height": 40,
                    "rendered_width": 160,
                    "rendered_height": 40,
                    "above_fold": true,
                    "largest_paint": false,
                    "alt": "present",
                    "findings": [],
                    "wasted_bytes": null,
                    "outcome": "skipped",
                    "skip_reason": "unsupported_format",
                    "image_id": null,
                    "format_sizes": null,
                    "suggested_alt": null
                }
            ]
        },
        "share_url": null,
        "created_at": "2026-10-01T11:05:00+00:00",
        "updated_at": "2026-10-01T11:05:41+00:00"
    }
}

Delete a page scan

DELETE /api/v1/scans/{id}

Deletes the scan and every image it produced, with their files, for good. A scan that is still running, or has an image still compressing, is refused, and so is a page of a site crawl, which goes with its crawl.

Path parameters

id
string (id) required
The scan's id.

Request

curl -X DELETE "https://www.iminify.com/api/v1/scans/9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7" \
  --connect-timeout 10 --max-time 1800 \
  -H "Authorization: Bearer $IMINIFY_API_KEY" \
  -H "Accept: application/json"

Response 204

No content.

Retry a page scan

POST /api/v1/scans/{id}/retry

Queues a failed or cancelled scan again with the settings it had. It counts as a scan.

Path parameters

id
string (id) required
The scan's id.

Request

curl -X POST "https://www.iminify.com/api/v1/scans/9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7/retry" \
  --connect-timeout 10 --max-time 30 \
  -H "Authorization: Bearer $IMINIFY_API_KEY" \
  -H "Accept: application/json"

Response 202

JSON
{
    "data": {
        "id": "9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7",
        "url": "https://example.com/pricing",
        "crawl_id": null,
        "status": "queued",
        "note": null,
        "settings": {
            "level": "smart",
            "format": "webp",
            "keep_metadata": false,
            "width": 1600,
            "height": null,
            "scale": null
        },
        "skipped": [],
        "images_count": 0,
        "images_in_progress": 0,
        "alt_texts_in_progress": 0,
        "images_finished": 0,
        "original_size": 0,
        "optimized_size": 0,
        "saved_bytes": 0,
        "saved_percent": null,
        "download_url": "https://www.iminify.com/api/v1/scans/9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7/download",
        "share_url": null,
        "created_at": "2026-10-01T11:05:00+00:00",
        "updated_at": "2026-10-01T11:05:00+00:00"
    }
}

Cancel a page scan

POST /api/v1/scans/{id}/cancel

Calls off a scan still waiting in the queue. Once the scanner has started, the scan runs to the end; its images can still be cancelled one by one.

Path parameters

id
string (id) required
The scan's id.

Request

curl -X POST "https://www.iminify.com/api/v1/scans/9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7/cancel" \
  --connect-timeout 10 --max-time 30 \
  -H "Authorization: Bearer $IMINIFY_API_KEY" \
  -H "Accept: application/json"

Response 200

JSON
{
    "data": {
        "id": "9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7",
        "url": "https://example.com/pricing",
        "crawl_id": null,
        "status": "cancelled",
        "note": null,
        "settings": {
            "level": "smart",
            "format": "webp",
            "keep_metadata": false,
            "width": 1600,
            "height": null,
            "scale": null
        },
        "skipped": [],
        "images_count": 0,
        "images_in_progress": 0,
        "alt_texts_in_progress": 0,
        "images_finished": 0,
        "original_size": 0,
        "optimized_size": 0,
        "saved_bytes": 0,
        "saved_percent": null,
        "download_url": "https://www.iminify.com/api/v1/scans/9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7/download",
        "share_url": null,
        "created_at": "2026-10-01T11:05:00+00:00",
        "updated_at": "2026-10-01T11:05:20+00:00"
    }
}

Download a page scan as a zip

GET /api/v1/scans/{id}/download

One zip with the optimized copy of every image of the scan that has finished, in the order the scanner found them. Images still compressing are left out, so wait for images_in_progress to reach 0 first.

The zip is laid out like the page: each image sits in the folders of its address (wp-content/uploads/hero.jpg), and an image from another host under a folder named after that host. It keeps its file name while its format is unchanged; a converted one takes its new extension. manifest.csv at the root lists every image address the scan fetched with its file in the zip, status, formats and sizes, then the addresses it passed over (a duplicate names the file it shares) and how many images your plan's per-scan limit left out.

Path parameters

id
string (id) required
The scan's id.

Request

curl --fail-with-body -o scan.zip "https://www.iminify.com/api/v1/scans/9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7/download" \
  --connect-timeout 10 --max-time 1800 \
  -H "Authorization: Bearer $IMINIFY_API_KEY"

Response 200

The file itself, not JSON.

Share a page report

POST /api/v1/scans/{id}/share

Gives the scan's page report a public page at an address nobody can guess, returned as share_url. Anyone with the link can read the report as you see it, with thumbnails of the compressed images, without signing in; nothing on it can be downloaded or changed. The page is kept out of search engines. Comes with a paid plan: the link answers only while your account is on a paid plan, and comes back if it lapses and renews. Sharing a report that is already shared keeps its link.

Path parameters

id
string (id) required
The scan's id.

Request

curl -X POST "https://www.iminify.com/api/v1/scans/9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7/share" \
  --connect-timeout 10 --max-time 30 \
  -H "Authorization: Bearer $IMINIFY_API_KEY" \
  -H "Accept: application/json"

Response 200

JSON
{
    "data": {
        "id": "9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7",
        "url": "https://example.com/pricing",
        "crawl_id": null,
        "status": "completed",
        "note": "Rendered in a headless browser. 14 image URLs found: 12 queued for compression, 1 duplicates of an image already queued, 1 in a format the compressor does not accept.",
        "settings": {
            "level": "smart",
            "format": "webp",
            "keep_metadata": false,
            "width": 1600,
            "height": null,
            "scale": null
        },
        "skipped": [
            {
                "url": "https://cdn.example.com/pricing/hero.jpg?v=2",
                "reason": "duplicate",
                "message": "The same image as https://example.com/images/hero.jpg, so it was compressed once."
            },
            {
                "url": "https://example.com/images/logo.svg",
                "reason": "unsupported_format",
                "message": "The file is SVG, a format Iminify does not compress."
            }
        ],
        "images_count": 12,
        "images_in_progress": 0,
        "alt_texts_in_progress": 0,
        "images_finished": 12,
        "original_size": 18406331,
        "optimized_size": 2114902,
        "saved_bytes": 16291429,
        "saved_percent": 88.51,
        "download_url": "https://www.iminify.com/api/v1/scans/9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7/download",
        "share_url": "https://www.iminify.com/r/k3Xw9QmT2vLp8RzN4bYc6HdJ",
        "created_at": "2026-10-01T11:05:00+00:00",
        "updated_at": "2026-10-01T11:05:41+00:00"
    }
}

Stop sharing a page report

DELETE /api/v1/scans/{id}/share

Switches the report's public link off. The page and its thumbnails answer 404 from the next request on. Sharing the report again later gives it a new link. Works on any plan, so a link can be taken down after a paid plan has ended.

Path parameters

id
string (id) required
The scan's id.

Request

curl -X DELETE "https://www.iminify.com/api/v1/scans/9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7/share" \
  --connect-timeout 10 --max-time 30 \
  -H "Authorization: Bearer $IMINIFY_API_KEY" \
  -H "Accept: application/json"

Response 204

No content.

Download a page report as CSV

GET /api/v1/scans/{id}/report.csv

The page audit as a spreadsheet, one row per image address in the page's order: url, the format and bytes a browser received, natural_width and natural_height (the file's own size), rendered_width and rendered_height (the size it is shown at), above_fold, largest_paint (yes, no, or empty when not known), alt, findings (separated by spaces), wasted_bytes, outcome, skip_reason, then the original_bytes of the file the scan downloaded and the compressed_format and compressed_bytes of its compressed copy. The fields mean what they mean on the scan's audit. A cell that would start with =, +, - or @ starts with an apostrophe, so a spreadsheet never runs it. Comes with a paid plan, for a scan that has finished with a report.

Path parameters

id
string (id) required
The scan's id.

Request

curl --fail-with-body -o report.csv "https://www.iminify.com/api/v1/scans/9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7/report.csv" \
  --connect-timeout 10 --max-time 1800 \
  -H "Authorization: Bearer $IMINIFY_API_KEY"

Response 200

The file itself, not JSON.

Download a page report as PDF

GET /api/v1/scans/{id}/report.pdf

The page report printed for a client or a colleague: the page, how many images need fixing, each finding with how to fix it, the page's largest paint, the bytes that resizing would save and the formats received, then every image to fix with its findings and its compressed size. The CSV lists every image. Printing takes a second or two, and one PDF prints at a time on your account: a second request while one prints is answered with rate_limited. Comes with a paid plan, for a scan that has finished with a report.

Path parameters

id
string (id) required
The scan's id.

Request

curl --fail-with-body -o report.pdf "https://www.iminify.com/api/v1/scans/9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7/report.pdf" \
  --connect-timeout 10 --max-time 1800 \
  -H "Authorization: Bearer $IMINIFY_API_KEY"

Response 200

The file itself, not JSON.