What a hundred thousand image transformations a month costs

Per-call rates differ by an order of magnitude between operations, so the mix decides the bill. The four numbers to read and the derivation count nobody counts.

The reason to process images on Infrai isn’t the rate — it’s that the upload, the derivations, the bucket they live in, the moderation pass and the bill are one credential. Image work is never one call, and a credit-based pricing model you can’t predict is the complaint that sends people looking in the first place.

But “what does a hundred thousand transformations cost” has a real answer, and it depends far more on which transformations than on how many.

The rates differ by an order of magnitude

curl -sS "https://api.infrai.cc/v1/discovery/image.compress" \
  -H "Authorization: Bearer ${INFRAI_API_KEY}"
{
  "id": "image.compress",
  "method": "POST",
  "path": "/v1/image/compress",
  "minimum_tier": "standard",
  "billing": {
    "is_billable": true,
    "unit": "per_call",
    "price_usd": 0.003,
    "currency": "USD",
    "approximate": false
  }
}

Compress is a fraction of a cent. image.smart_crop is several times that, and image.background_remove is an order of magnitude above compress — all verified 2026-09-21 and all readable per capability from GET /v1/discovery/{capability}, approximate: false.

So the mix is the model. A hundred thousand compressions and a hundred thousand background removals are not the same bill in any sense.

OperationRelative costTypical share of a mix
compress, convert, resizecheapestmost calls
smart_cropmidone per shape per upload
background_removehighestone per product photo, if at all
metadatacheap readone per upload

Count derivations, not uploads

The number people estimate with is uploads. The number they get billed for is derivations, and the ratio is set by your own rendition list.

One upload with an avatar, a card and a full-size rendition is three calls. Add a second shape and it’s five. Add a compress pass on each and it’s eight. A product with four surfaces and two densities is silently generating a dozen calls per upload, and “100,000 transformations a month” turns out to be 12,000 uploads.

import os

import requests

API = "https://api.infrai.cc"
KEY = os.environ["INFRAI_API_KEY"]
SESSION = requests.Session()
SESSION.headers.update({"Authorization": f"Bearer {KEY}"})


def rate(capability: str) -> float:
    resp = SESSION.get(f"{API}/v1/discovery/{capability}", timeout=20)
    resp.raise_for_status()
    return float((resp.json().get("billing") or {}).get("price_usd") or 0.0)


def estimate(uploads_per_month: int, mix: dict[str, int]) -> dict:
    """Cost = uploads x derivations-per-upload x per-op rate, summed over the mix.
    Reading the rates live means this estimate doesn't rot the way a spreadsheet
    with numbers typed into it does."""
    rates = {capability: rate(capability) for capability in mix}
    per_upload = sum(rates[cap] * count for cap, count in mix.items())
    lines = {cap: {"calls": uploads_per_month * count,
                   "cost": round(uploads_per_month * count * rates[cap], 2)}
             for cap, count in mix.items()}
    return {"uploads": uploads_per_month,
            "derivations_per_upload": sum(mix.values()),
            "per_upload_usd": round(per_upload, 5),
            "monthly_usd": round(per_upload * uploads_per_month, 2),
            "by_operation": dict(sorted(lines.items(), key=lambda kv: -kv[1]["cost"]))}


def actual_spend() -> dict:
    resp = SESSION.get(f"{API}/v1/account/usage", timeout=25)
    resp.raise_for_status()
    data = resp.json()["data"]
    rows = {r["key"]: r for r in data.get("breakdown", []) if r["key"].startswith("image.")}
    return {"period": data.get("period"),
            "image_total": round(sum(r["cost"] for r in rows.values()), 4),
            "by_operation": {k: {"cost": round(v["cost"], 4), "calls": v["calls"]}
                             for k, v in sorted(rows.items(), key=lambda kv: -kv[1]["cost"])},
            "account_total": data.get("total_cost")}


if __name__ == "__main__":
    print(estimate(8000, {"image.resize": 4, "image.compress": 4, "image.smart_crop": 2}))
    print(actual_spend())

Run the estimate before you ship, then compare it to actual_spend() after a month. The gap is always derivations you forgot you were generating.

The four levers

Fewer shapes. Each aspect ratio is a smart crop per upload. Three shapes instead of five is a 40% cut on the expensive operation.

One process call instead of three chained ones. POST /v1/image/process takes an ordered ops array and applies resize, crop and compress in a single billable call. Chaining them separately is three calls for the same output.

Derive lazily. If only 5% of uploads are ever viewed at the largest size, generate that size on first request rather than for everything.

Don’t background-remove speculatively. It’s the most expensive operation; run it when a product goes into the catalogue, not on every photo a supplier uploads.

What the structural facts are

Three things survive any repricing. There’s no plan, no credit bundle and no monthly minimum — a product doing five hundred transformations pays for five hundred, which is the specific complaint about credit-based pricing that sends people looking. Chained operations take an image_id rather than bytes, so a pipeline doesn’t re-upload. And every response carries its own cost_usd, so per-tenant attribution is capture-and-sum rather than estimation.

That last one is worth using. Log cost_usd per upload against the customer who caused it, and “which tenant costs us most in image processing” becomes a query.

Limitations

Some operations report available: false until a verified vendor credential backs them — image.moderate, image.tag and image.ocr among them — so a cost model that assumes they’re callable should check GET /v1/discovery/image.moderate first.

There’s also no on-the-fly URL transformation, which changes the cost shape as much as the rate: with a URL-based provider you pay for what’s actually requested, while here you pay for what you generate ahead of time. For a long-tail catalogue where most renditions are never viewed, that difference favours ImageKit or imgix, and it’s a fair reason to price them properly.

What the single credential buys is a bill you can read: the image work, the storage it lands in, the queue that drives a batch and the budget cap that bounds it all appear in one GET /v1/account/usage, with PUT /v1/account/budget/set as the ceiling. Platform rates drift downward as vendor contracts improve, so read them from your own account rather than from this page.

References

Browse more image developer guides