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, and how many page scans you can run,
  • how many images one page scan compresses,
  • 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, so a script can read them rather than hard-code them.

Three 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, and Iminify fetches at most 2 image addresses for you at a time.

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. That isn't a per-minute count: it clears as soon as one of them 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.
  • The website and the API spend the same counts.

When the 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 without a daily cap 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.
set -euo pipefail

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

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

  if [ "$status" != "429" ]; then
    cat response.json
    exit 0
  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')
  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.