Reference

Site crawls API

Crawl several pages of one site at once, from its sitemap or a list, with one report and one zip for the whole site. Every site crawls endpoint with samples.

A site crawl scans several pages of one site in one go. Give it the site's sitemap, or a list of its pages, and each page is scanned as a page scan of its own, with the settings you send. An image the pages share is compressed once, and the crawl gives you one report and one zip for the whole site.

Site crawls are a Pro feature, on the website's scan page as well as here; another plan is answered with pro_required. A crawl started on Pro can still be read, downloaded and deleted after Pro ends.

Starting one

POST /crawls takes either sitemap or pages, not both, plus the same compression settings as a scan:

  • sitemap is the address of an XML sitemap, a sitemap index or a text file with one address a line, gzipped or not. The crawl reads up to 10 MB of it and takes its first pages in the sitemap's order, only those on the sitemap's own site (with or without www.). From a sitemap index it opens at most 5 of the sitemaps it lists, in order, until it has enough pages, and passes over one it can't read.
  • pages is a list of page addresses on one site. An address listed twice is crawled once, and https:// can be left out of each. A list longer than your plan's pages per crawl is refused rather than cut, and so is one that mixes two sites.

Each crawl counts one against your daily site crawls as soon as it's queued, even if its sitemap turns out to be unreadable. Its pages don't count against your daily page scans, and their images don't count against your daily images. Each page stops at your plan's images per scan, as any scan does. The numbers for every plan are on the pricing page, and yours are in GET /account under limits.crawls_per_day and limits.pages_per_crawl.

A sitemap with more pages than the crawl takes says how many it left out in pages_left_out. For a sitemap index that's a floor, because the crawl stops reading once it has enough.

The life of a crawl

TEXT
queued ──► reading ──► scanning ──► completed
   │           │
   └───────────┴──► failed

reading means the sitemap is being fetched, and a crawl from a list passes through it in a moment. In scanning its pages have been handed to the scanner, and they and their images are still at work. It turns completed once every page has finished, images included. failed means the crawl couldn't start its pages, and note says why, such as a sitemap that couldn't be fetched or read, or one that listed no page on its own site.

Poll GET /crawls/{id} every five seconds or so. The crawl is done once its status is completed or failed and both pages_in_progress and images_in_progress are 0. A failed crawl can still have pages it handed over before it stopped; they finish on their own.

Its pages

A single crawl lists its pages in pages, each with its status and image counts. Each one is a page scan: get it for its images, the addresses it passed over and its own page report, and list its images with the image list's scan filter. A page scan names its crawl in crawl_id.

The pages stay out of the scan list, because the crawl lists them. A page can be retried or cancelled like any scan, and a retry counts against your daily page scans. It can't be deleted on its own: the pages share the images they have in common, so a page goes with its crawl, and deleting one is answered with part_of_crawl.

When two pages show the same image, the first page to reach it compresses it, and the other lists it in skipped as a duplicate of that address. Its report entry points at the image the other page compressed (image_id). Two pages that reach the same image at the same moment can both compress it.

The site's report

Once nothing in the crawl is at work, a single crawl carries the site's report in audit: how many image addresses the pages had and how many to fix, each finding's count, what the oversized images would shed, and the bytes a browser downloaded. An image the pages share counts once. Its findings depend on the page showing it (the size it's shown at, the first screen), so it counts under a finding when any page gave it that finding, and its waste is the most any page measured. Each entry in pages also says how many of that page's images to fix, in images_to_fix.

Each page's own report, with every image and what's worth fixing about it, is on its scan, and on Pro it can be shared or exported as CSV or PDF the way any scan's can.

The zip

GET /crawls/{id}/download gives one zip with a folder for each page, named after the page's path: https://example.com/blog/hello-world becomes blog-hello-world/, and the front page home/. Each folder is laid out like that page's own scan zip, with its own manifest.csv.

An image the pages share sits only in the folder of the page that compressed it, and the other pages' manifests point at it there, every path given from the zip's root. The zip is ready once the crawl is done; until then it's refused with scan_in_progress.

Checking the site every week

On Pro, a crawl can become a weekly site check: POST /monitors with its id, and every week the site is crawled again the same way, compressing only the images that are new or changed. Each week's check is a crawl of its own, named by its check in monitor_id, and stays out of the crawl list.

Webhooks

A crawl sends no event of its own. On Pro, each of its pages sends scan.completed or scan.failed like any scan, with the crawl's id in crawl_id, and each image sends its own image event. See Webhooks.

Through an AI agent

The MCP server's crawl_site starts a crawl, get_crawl follows it and carries the site's report, and create_zip with crawl_id downloads the zip. See the MCP server.

The Crawl object

id string
The crawl's id.
url string
The sitemap the crawl read, or the first of the pages it was given.
source string
Where its pages came from: sitemap or pages, a list.
monitor_id string or null
The weekly site check this crawl is a run of, or null for a crawl you started. A check's runs are left out of the crawl list; the check names its newest.
status string
Where the crawl is. queued: waiting to start. reading: its sitemap is being read. scanning: its pages have been handed to the scanner and they, or their images, are still at work. completed: every page has finished, its images included. failed: the crawl could not start its pages (note says why); a failed crawl may still have pages it started, which finish on their own.
note string or null
Why the crawl failed, or null.
settings object
The settings every image of every page 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.
pages_count integer
How many pages the crawl took. 0 until its sitemap has been read.
pages_in_progress integer
How many of them are still queued or being scanned.
pages_failed integer
How many of them failed. Each one's note says why.
pages_left_out integer
How many more pages the sitemap or the list had past your plan's pages per crawl, which were not scanned. For a sitemap index, a floor: it stops reading once it has enough.
images_count integer
How many images the pages queued. An image the pages share is compressed once, so it counts once.
images_in_progress integer
How many of them are still queued or being compressed. The crawl is done once its status is completed or failed and both pages_in_progress and this are 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 page's images as one zip, a folder per page with its manifest, once the crawl is done.
pages array
The pages, in the order the crawl took them, each a page scan of its own. Only on a single crawl, not in a list.
audit string or null
The site's report: every page's audit added up, an image the pages share counted once. null while any page or image is at work, and for a crawl with no page. Only on a single crawl, not in a list.
created_at timestamp
When the crawl was queued.
updated_at timestamp
When it last changed.

The CrawlPage object

id string
The page scan's id, for GET /scans/{id}, its report, and its images in the image list's scan.
url string
The page.
status string
Where the scanner is, as on a page scan.
note string or null
What the scanner wrote when it finished, or why it failed. null until then.
images_count integer
How many images the page queued. An image another page of the crawl compressed first is not among them.
images_in_progress integer
How many of them are still queued or being compressed.
images_to_fix integer or null
How many image addresses the page's report found something worth fixing about, or null until the crawl's report exists.

The CrawlAudit object

pages_audited integer
How many of the pages have a report. A page that failed has none.
images_found integer
How many image addresses the pages had, each counted once however many pages show it.
images_to_fix integer
How many of them at least one page found something worth fixing about.
findings object
How many image addresses have each finding on at least one page. An image's findings depend on the page showing it (its size there, the first screen), so one page can flag an image another doesn't.
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).
findings.lazy_above_fold integer
Images in the first screen that are lazy loaded.
findings.eager_below_fold integer
Images below the first screen that are not lazy loaded.
findings.missing_alt integer
Images without alt text.
findings.lcp_not_prioritized integer
Images that are a page's largest paint and lack fetchpriority="high".
oversized_bytes integer
About how many bytes the oversized images would shed if each were served at twice the size it is shown, each address once, at the most any page measured.
bytes integer
The bytes a browser downloaded for the images, each address once.

List site crawls

GET /api/v1/crawls

Every site crawl on your account, newest first, each with its counts and totals. The pages themselves come with a single crawl. The crawls a weekly site check runs by itself are not listed.

Query parameters

per_page
integer
Crawls 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/crawls?per_page=5" \
  --connect-timeout 10 --max-time 30 \
  -H "Authorization: Bearer $IMINIFY_API_KEY" \
  -H "Accept: application/json"

Response 200

JSON
{
    "data": [
        {
            "id": "9d3c4e07-5c6d-4e7f-9a0b-c1d2e3f4a5b6",
            "url": "https://example.com/sitemap.xml",
            "source": "sitemap",
            "monitor_id": null,
            "status": "completed",
            "note": null,
            "settings": {
                "level": "smart",
                "format": "webp",
                "keep_metadata": false,
                "width": 1600,
                "height": null,
                "scale": null
            },
            "pages_count": 2,
            "pages_in_progress": 0,
            "pages_failed": 0,
            "pages_left_out": 0,
            "images_count": 12,
            "images_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/crawls/9d3c4e07-5c6d-4e7f-9a0b-c1d2e3f4a5b6/download",
            "created_at": "2026-10-01T11:04:50+00:00",
            "updated_at": "2026-10-01T11:04:55+00:00"
        }
    ],
    "links": {
        "first": "https://www.iminify.com/api/v1/crawls?page=1",
        "last": "https://www.iminify.com/api/v1/crawls?page=1",
        "prev": null,
        "next": null
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 1,
        "links": [
            {
                "url": null,
                "label": "« Previous",
                "page": null,
                "active": false
            },
            {
                "url": "https://www.iminify.com/api/v1/crawls?page=1",
                "label": "1",
                "page": 1,
                "active": true
            },
            {
                "url": null,
                "label": "Next »",
                "page": null,
                "active": false
            }
        ],
        "path": "https://www.iminify.com/api/v1/crawls",
        "per_page": 15,
        "to": 1,
        "total": 1
    }
}

Crawl a site

POST /api/v1/crawls

A Pro feature. Reads the site's sitemap, or takes the pages you list, and scans each page as a page scan of its own, with the settings you send. An image the pages share is compressed once, unless two pages reach it at the same moment. It answers straight away with the status queued; a crawl takes a few minutes. Each crawl counts one against your daily site crawls, even when its sitemap turns out to be unreadable; its pages don't count against your daily page scans, nor their images against your daily images.

Each page stops at your plan's images per scan, as a scan does. A sitemap on a private network is refused, and so is a page on one.

Body (JSON)

sitemap
string (URL)
The address of the site's sitemap: an XML sitemap, a sitemap index or a text file with one address a line, gzipped or not. The crawl takes its first pages on the sitemap's own site, with or without www., up to your plan's pages per crawl. https:// can be left out. Send this or pages, not both.
pages
array of strings
The pages to crawl, all on one site (with or without www.), up to your plan's pages per crawl. https:// can be left out of each, and an address listed twice is crawled once. A longer list is refused, not cut. Send this or sitemap, not both.
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 a page shows with no alt attribute at all, from its compressed copy, for that page's report (suggested_alt on its scan). On by default; send false to leave it out. 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/crawls" \
  --connect-timeout 10 --max-time 30 \
  -H "Authorization: Bearer $IMINIFY_API_KEY" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"sitemap":"https://example.com/sitemap.xml","level":"smart","format":"webp"}'

Response 202

JSON
{
    "data": {
        "id": "9d3c4e07-5c6d-4e7f-9a0b-c1d2e3f4a5b6",
        "url": "https://example.com/sitemap.xml",
        "source": "sitemap",
        "monitor_id": null,
        "status": "queued",
        "note": null,
        "settings": {
            "level": "smart",
            "format": "webp",
            "keep_metadata": false,
            "width": 1600,
            "height": null,
            "scale": null
        },
        "pages_count": 0,
        "pages_in_progress": 0,
        "pages_failed": 0,
        "pages_left_out": 0,
        "images_count": 0,
        "images_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/crawls/9d3c4e07-5c6d-4e7f-9a0b-c1d2e3f4a5b6/download",
        "created_at": "2026-10-01T11:04:50+00:00",
        "updated_at": "2026-10-01T11:04:50+00:00"
    }
}

Get a site crawl

GET /api/v1/crawls/{id}

One crawl with its pages and, once nothing is at work, the site's report. Poll this while a crawl runs: it is done once its status is completed or failed and both pages_in_progress and images_in_progress are 0. Each page is a page scan: get it for its images, its skipped addresses and its own report.

Path parameters

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

Request

curl "https://www.iminify.com/api/v1/crawls/9d3c4e07-5c6d-4e7f-9a0b-c1d2e3f4a5b6" \
  --connect-timeout 10 --max-time 30 \
  -H "Authorization: Bearer $IMINIFY_API_KEY" \
  -H "Accept: application/json"

Response 200

JSON
{
    "data": {
        "id": "9d3c4e07-5c6d-4e7f-9a0b-c1d2e3f4a5b6",
        "url": "https://example.com/sitemap.xml",
        "source": "sitemap",
        "monitor_id": null,
        "status": "completed",
        "note": null,
        "settings": {
            "level": "smart",
            "format": "webp",
            "keep_metadata": false,
            "width": 1600,
            "height": null,
            "scale": null
        },
        "pages_count": 2,
        "pages_in_progress": 0,
        "pages_failed": 0,
        "pages_left_out": 0,
        "images_count": 12,
        "images_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/crawls/9d3c4e07-5c6d-4e7f-9a0b-c1d2e3f4a5b6/download",
        "pages": [
            {
                "id": "9d3c4f2a-1b2c-4d3e-8f4a-5b6c7d8e9f01",
                "url": "https://example.com/",
                "status": "completed",
                "note": "Rendered in a headless browser. 1 image URL found: 1 in a format the compressor does not accept.",
                "images_count": 0,
                "images_in_progress": 0,
                "images_to_fix": 0
            },
            {
                "id": "9d3c4f12-7a8b-4c9d-a0e1-f2a3b4c5d6e7",
                "url": "https://example.com/pricing",
                "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.",
                "images_count": 12,
                "images_in_progress": 0,
                "images_to_fix": 2
            }
        ],
        "audit": {
            "pages_audited": 2,
            "images_found": 4,
            "images_to_fix": 2,
            "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
        },
        "created_at": "2026-10-01T11:04:50+00:00",
        "updated_at": "2026-10-01T11:04:55+00:00"
    }
}

Delete a site crawl

DELETE /api/v1/crawls/{id}

Deletes the crawl, every page it scanned and every image they produced, with their files, for good. The pages share the images they have in common, so they go with their crawl, never one by one. A crawl with a page or an image still at work is refused.

Path parameters

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

Request

curl -X DELETE "https://www.iminify.com/api/v1/crawls/9d3c4e07-5c6d-4e7f-9a0b-c1d2e3f4a5b6" \
  --connect-timeout 10 --max-time 30 \
  -H "Authorization: Bearer $IMINIFY_API_KEY" \
  -H "Accept: application/json"

Response 204

No content.

Download a site crawl as a zip

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

One zip with a folder for each page, named after the page's path (blog-hello-world, and home for the front page). Each folder is laid out like that page's own scan zip, with its manifest.csv. An image the pages share is in the folder of the page that compressed it, and the other pages' manifests point at it there, every path from the zip's root. It is ready once the crawl is done, so a crawl still at work is refused.

Path parameters

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

Request

curl --fail-with-body -o crawl.zip "https://www.iminify.com/api/v1/crawls/9d3c4e07-5c6d-4e7f-9a0b-c1d2e3f4a5b6/download" \
  --connect-timeout 10 --max-time 1800 \
  -H "Authorization: Bearer $IMINIFY_API_KEY"

Response 200

The file itself, not JSON.