Guides

API rate limits and quotas

Requests a minute, compressions a minute, images and page scans a day, file size and batch size for each Iminify plan, and how to handle a 429.

Two kinds of limit apply: how fast you call (per minute) and how much you compress (per day). Both belong to your account and your plan, not to a key, so two keys on one account share them.

What your plan sets

Your plan decides:

  • how many images you can compress a day, how many page scans you can run, and how many AI alt texts you get,
  • how many files your upload groups can make a day,
  • how many images one page scan compresses,
  • how many site crawls you can start a day, and how many pages one crawl scans (site crawls come with a paid plan),
  • how many sites can have a weekly site check at once (also a paid plan's),
  • the largest file you can upload or have fetched,
  • how many compressions and how many page scans you can start a minute,
  • how many API requests you can make a minute.

The numbers for every plan are on the pricing page. Your own, with what you've used today, are one call away at GET /account, under limits and usage (crawls_per_day and pages_per_crawl for site crawls, monitors for weekly site checks, files_per_day for upload groups), so a script can read them rather than hard-code them.

Some limits are the same on every plan: an image can have at most 64 megapixels, before and after a resize, a batch call takes at most 150 ids, Iminify fetches at most 2 image addresses for you at a time, and an upload group makes at most 30 outputs, 12 of them cropped, and takes its image within 60 minutes of opening (its rules).

The API is for accounts with a verified email address; a guest, or an account that hasn't verified yet, can't use it.

Per minute

Requests a minute counts every call you make to the API, reads included. Compressions a minute counts every compression queued, which is an upload, a recompress or a retry, and page scans a minute counts every scan started or retried. All three windows are a minute long and counted per account.

The compressions and the page scans are the same counts the website spends. An image you drop on the website and one you send to the API come out of one minute, not one each, so running both at once never gets you more than your plan allows.

Every answer carries your request limit in its X-RateLimit-Limit header and what is left of the minute in X-RateLimit-Remaining, so you can slow down before you hit it.

Go over any of the three and the answer is 429 with the code rate_limited and a Retry-After header in seconds. Wait that long and send the same request again. Nothing was done and nothing was counted.

The same answer comes back when you send an image address while 2 of yours are still being fetched, and when you open an upload group while your groups already have many files waiting to be made. Neither is a per-minute count: each clears as work finishes, usually within seconds.

Per day

The daily counts are a rolling 24 hours that start with your first image (or scan), not midnight. usage.images.resets_at in GET /account says when yours opens again.

  • An image counts once it's queued: an upload, a recompress or a retry. A refused request, a failed validation or a storage hiccup costs nothing.
  • A page scan counts one scan. The images it finds don't count against your daily images; they're held to the plan's images per scan instead.
  • A site crawl counts one crawl 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.
  • An upload group counts one image, and a file against your daily files for each output in each format, as soon as it opens. A group past any limit is refused whole, with nothing counted. If its image never arrives, what it counted is given back.
  • A weekly site check doesn't count against your daily site crawls, page scans or images, neither when you start it nor when it runs. The AI alt texts its runs write count as any do.
  • An AI alt text counts once it's asked for, with an image or on its own. One copied from an image with the same bytes costs nothing, and an image sent once they're used up still compresses, without one.
  • The website and the API spend the same counts.

When a day's count is spent, the answer is 429 with the code daily_limit_reached and a Retry-After header holding the seconds until the window opens again. That can be hours, so don't retry it in a loop. A plan sold as unlimited is still a service shared with other people, so traffic that degrades it for everyone can be slowed down.

Handling a 429

Read Retry-After, wait, send the request again. For rate_limited that's seconds; for daily_limit_reached it can be most of a day, so decide whether your script should wait or stop.

#!/usr/bin/env bash
# Send a request, and when the API answers 429, wait as long as it asks and send it again.
# The call gives up after 10 seconds without a connection and after 30 seconds overall,
# so a stalled network can't hang the script.
set -euo pipefail

API="https://www.iminify.com/api/v1"

for attempt in 1 2 3 4 5; do
  status=$(curl -sS --connect-timeout 10 --max-time 30 -o response.json -D headers.txt -w '%{http_code}' "$API/account" \
    -H "Authorization: Bearer $IMINIFY_API_KEY" -H "Accept: application/json")

  if [ "$status" -lt 400 ]; then
    cat response.json
    exit 0
  fi

  if [ "$status" != "429" ]; then
    cat response.json >&2
    exit 1
  fi

  # A daily limit can ask for hours; decide whether your script should wait that long.
  wait=$(grep -i '^retry-after:' headers.txt | tr -dc '0-9' || true)
  sleep "${wait:-60}"
done

exit 1

Polling without wasting requests

Polling one image every two seconds costs 30 requests a minute, which leaves most plans plenty of room; your own limit is in X-RateLimit-Limit. For a batch, poll the list with status=optimizing or status=queued instead of each image, or poll only the images you still wait for. A page scan needs one poll every five seconds or so: GET /scans/{id} carries every image's status at once.