---
title: "Scan a page"
method: POST
path: "/scans"
tags: ["Page scans"]
---

# Scan a page

`POST /scans`

## Request

- Base URL: `https://www.iminify.com/api/v1`
- URL: `https://www.iminify.com/api/v1/scans`
- Auth: HTTP bearer

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.

## Request body

- StoreScanRequest: The page to scan and the settings every image found on it is compressed with.
  - `url` string, uri, required: The page to scan. `https://` can be left out.
  - `level` 'none' | 'lossless' | 'ultra' | 'smart'
  - `format` '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.
  - `width` integer, nullable: 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.
  - `height` integer, nullable: Resize to this height in pixels, the same way as `width`. Up to 65535.
  - `scale` integer, nullable: Resize to this percentage of the original, 1 to 100. Cannot be combined with `width` or `height`.
  - `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](/docs/images#alt-text-and-file-names).
  - `alt_text_language` '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': 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.

## Response `202`

Queued. `Location` points at the new record.

- object
  - `data` Scan, required
    - `id` string, uuid, required: The scan's id.
    - `url` string, uri, required: The page that was scanned.
    - `status` 'queued' | 'scanning' | 'completed' | 'failed' | 'no-image-found' | 'cancelled', required
    - `note` string, nullable, required: 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, required: The settings every image the scan found is compressed with.
      - `level` 'none' | 'lossless' | 'ultra' | 'smart', required
      - `format` 'auto' | 'webp' | 'avif' | 'png' | 'jpg', required
      - `keep_metadata` boolean, required: Whether EXIF metadata is kept.
      - `width` integer, nullable, required: The width every image is resized to, or `null`.
      - `height` integer, nullable, required: The height every image is resized to, or `null`.
      - `scale` integer, nullable, required: The percentage every image is scaled to, or `null`.
    - `skipped` object[], required: 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), `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.
      - `url` string, required
      - `reason` 'duplicate' | 'unsupported_format' | 'too_large' | 'tracking_pixel' | 'private_network' | 'unreadable' | 'unreachable' | 'refused' | 'failed', required: Why a page scan passed over an image it found on the page.
      - `message` string, required
    - `images_count` integer, required: How many images the scan queued.
    - `images_in_progress` integer, required: 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, required: 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, required: How many of them have finished.
    - `original_size` integer, required: Bytes of the finished images before compression.
    - `optimized_size` integer, required: Bytes of the same images after compression.
    - `saved_bytes` integer, required: The difference between the two.
    - `saved_percent` number, nullable, required: The same saving as a percentage, or `null` while no image has finished.
    - `download_url` string, uri, required: Where to download every finished image as one zip.
    - `images` Image[]: The scan's images, newest first. Only on a single scan, not in a list.
      - `id` string, uuid, required: The image's id.
      - `status` 'queued' | 'optimizing' | 'completed' | 'failed' | 'already-optimized' | 'cancelled', required
      - `name` string, required: The name the optimized copy downloads under: the uploaded name, or the one you gave it, with the delivered format's extension. An `auto` conversion has no format until it finishes, so until then the extension is the uploaded file's own.
      - `original` object, required: The file as it was uploaded.
        - `name` string, required: The file name it downloads under.
        - `format` string, required: The file's format, as its extension: `jpg`, `png`, `webp`, `avif`, `gif`, `heic`, `tiff` and so on.
        - `size` integer, required: Its size in bytes.
        - `width` integer, required: Its width in pixels, the way it displays: a phone photo stored on its side is measured upright.
        - `height` integer, required: Its height in pixels, the way it displays.
        - `download_url` string, uri, required: Where to download it with your key.
      - `optimized` object, nullable, required: The optimized copy, or `null` until the image has finished.
        - `name` string, required: The file name it downloads under.
        - `format` string, required: The file's format, as its extension: `jpg`, `png`, `webp`, `avif`, `gif`, `heic`, `tiff` and so on.
        - `size` integer, required: Its size in bytes.
        - `width` integer, required: Its width in pixels, the way it displays: a phone photo stored on its side is measured upright.
        - `height` integer, required: Its height in pixels, the way it displays.
        - `download_url` string, uri, required: Where to download it with your key.
      - `saved_bytes` integer, nullable, required: Bytes the optimized copy saves against the original. Negative when a conversion or an enlargement made the file bigger. `null` until the image has finished.
      - `saved_percent` number, nullable, required: The same saving as a percentage of the original, to two decimal places. `null` until the image has finished.
      - `settings` object, required: The settings the image was compressed with, as they were sent.
        - `level` 'none' | 'lossless' | 'ultra' | 'smart', required
        - `format` 'auto' | 'webp' | 'avif' | 'png' | 'jpg', required
        - `keep_metadata` boolean, required: Whether the EXIF metadata was kept.
        - `width` integer, nullable, required: The width it was resized to, or `null`.
        - `height` integer, nullable, required: The height it was resized to, or `null`.
        - `scale` integer, nullable, required: The percentage it was scaled to, or `null`.
      - `alt_text` object, nullable, required: The AI alt text and suggested file name, or `null` when none was asked for. See [Alt text and file names](/docs/images#alt-text-and-file-names).
        - `status` 'pending' | 'written' | 'failed' | 'skipped', required: Where a result's AI alt text stands. `pending`: asked for and not written yet (only a finished result shows it as in progress; a failed compression keeps the request for its retry). `written`: the text and the suggested file name are stored. `failed`: the model refused, or the call or its thumbnail failed; it can be asked for again. `skipped`: asked for on a day whose alt texts were spent.
        - `text` string, nullable, required: One sentence for the image's `alt` attribute, or `null` until it is written.
        - `language` string, nullable, required: The language tag it was written in.
        - `suggested_name` string, nullable, required: A short descriptive file name without an extension, ready for the rename call, or `null` until it is written or when the model offered none.
      - `scan_id` string, uuid, nullable, required: The page scan that found the image, or `null` for an upload.
      - `source_url` string, uri, nullable, required: The address the image was fetched from: the image's address on the scanned page, or the `url` you sent instead of a file. `null` for a file you sent.
      - `share_url` string, uri, nullable, required: The public page while the image is shared, or `null`.
      - `created_at` string, date-time, required: When the image was queued.
      - `updated_at` string, date-time, required: When anything about it last changed, its status included.
    - `audit` ScanAudit
      - `rendered` boolean, required: 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, required: How many image addresses the page had. `images` lists them all unless a page had more than 1000.
      - `largest_paint` object, nullable, required: 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.
        - `kind` 'image' | 'text' | 'none', required: `image` (a picture or a background), `text`, or `none` when the page painted nothing.
        - `element` string, nullable, required: The element's tag, such as `img`, `h1` or `div`.
        - `url` string, nullable, required: The image's address, when it is one. The image's own entry in `images` says `largest_paint: true`.
      - `findings` object, required: How many images have each finding.
        - `oversized` integer, required: Images whose file is more than twice as wide and twice as tall as the space they are shown in.
        - `without_dimensions` integer, required: Images shown without width and height attributes (and no CSS aspect ratio), so the page can move as they load.
        - `lazy_above_fold` integer, required: Images in the first screen that are lazy loaded, which delays them.
        - `eager_below_fold` integer, required: Images below the first screen that are not lazy loaded, so they load before they are needed.
        - `missing_alt` integer, required: Images without alt text. An empty `alt=""`, which marks an image as decorative, is not counted.
        - `lcp_not_prioritized` integer, required: 1 when the largest paint is an image without `fetchpriority="high"`, which would have the browser fetch it first.
      - `oversized_bytes` integer, required: 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, required: The bytes a browser downloaded for the images listed.
      - `formats` object[], required: 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.
        - `format` string, required
        - `count` integer, required
      - `images` ScanAuditImage[], required: Every image address the page had, in the page's order.
        - `url` string, uri, required: The image's address on the page.
        - `format` string, nullable, required: 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, nullable, required: The bytes a browser downloaded for it, or `null` when it did not load as an image.
        - `natural_width` integer, nullable, required: 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, nullable, required: The file's own height in pixels.
        - `rendered_width` integer, nullable, required: 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, nullable, required: The height it is shown at.
        - `above_fold` boolean, nullable, required: Whether it is in the first screen before scrolling, or `null` when that is not known.
        - `largest_paint` boolean, required: Whether it is the page's largest contentful paint.
        - `alt` 'missing' | 'empty' | 'present', required: What an image on a scanned page says to a screen reader. `missing`: no alt text at all, which is the finding. `empty`: alt="" (or marked decorative), which is right for an image that carries no meaning. `present`: it has a text alternative.
        - `findings` AuditFinding[], required: What is worth fixing about it, empty when nothing.
        - `wasted_bytes` integer, nullable, required: About how many bytes it would shed at twice the size it is shown, when it is oversized; otherwise `null`.
        - `outcome` 'compressed' | 'skipped' | 'left_out', required: 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`. `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` 'duplicate' | 'unsupported_format' | 'too_large' | 'tracking_pixel' | 'private_network' | 'unreadable' | 'unreachable' | 'refused' | 'failed', required: Why a page scan passed over an image it found on the page.
        - `image_id` string, uuid, nullable, required: The id of its compressed image, or `null`.
        - `format_sizes` object[], nullable, required: 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`.
          - `format` string, required
          - `bytes` integer, required
        - `suggested_alt` string, nullable, required: 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](/docs/images#alt-text-and-file-names).
    - `share_url` string, uri, nullable, required: The page report's public link while it is shared, or `null`. The link answers only while your plan includes Pro.
    - `created_at` string, date-time, required: When the scan was queued.
    - `updated_at` string, date-time, required: When it last changed.

## Other responses

- `401`: `unauthenticated`: No key was sent, or the key does not exist (it was mistyped or deleted). Send it as `Authorization: Bearer <key>`.
- `403`: `email_not_verified`: The key belongs to an account whose email address is not verified yet. Follow the link in the verification email, then call again with the same key. `account_banned`: The account behind the key has been banned. Its keys stop working with the ban. `scans_not_available`: Your plan does not include page scans.
- `422`: `validation_failed`: A field is missing or wrong, or the file or address did not pass the checks every upload goes through. `errors` names each field with its messages.
- `429`: `rate_limited`: One of your per-minute limits is spent: requests, compressions or page scans. The compressions and scans are counted together with the website's, so work queued there counts too. Nothing was done; wait for the number of seconds in the `Retry-After` header and send it again. `daily_limit_reached`: Your plan's images, page scans or AI alt texts for the day are used up. `Retry-After` holds the seconds until the rolling 24-hour window opens again. An image sent while only the alt texts are used up still compresses, without one.

---

[API](https://skmtc.dev/iminify/apis/iminify-api.md) · [All operations](https://skmtc.dev/iminify/apis/iminify-api/llms.txt) · [OpenAPI document](https://skmtc.dev/iminify/apis/iminify-api/revisions/12dc614c2c21?raw)
