The Sizy mark: a filled box inside the dashed one it was cropped out of

Add ?width=400. That's the integration.

Sizy is an image CDN that fronts pictures you already host. Point a bucket at your origin, then ask for a size in the query string — nothing is migrated, nothing is installed, and the URL you write is the whole API surface.

$0.40 per 1,000 transforms · 5,000 free every month · delivery is never billed

The sample photograph, transformed by Sizy at the size and fit you chose
Source 4,896×3,264 · 16.0 MP, 2.1 MB
Presets
fit

Fill the box and crop whatever hangs over the edge. The default, and what a thumbnail wants.

position

center

https://sizy.io/sizy/samples/img.jpg?width=480&height=320&fit=cover

Not a mock-up and not a screenshot. That image was fetched from sizy.io by your browser using the URL under it, and if nobody had asked for that exact combination before, it was rendered while you were reading this sentence.

Nothing moves.

Your originals stay on whatever already serves them — S3, a CDN, a folder on a box. A bucket is two fields: a name and the origin to fetch from. There is no upload step, no migration and no second copy of your library to keep in step.

Nothing installs.

No SDK, no build plugin, no loader, no framework integration. A plain GET with a query string on it, which means every language, every template engine and every CMS already supports it.

Delivery is not a line item.

Serving an image is free at any volume, and always has a fair-use edge published rather than implied — about 500 deliveries per transform a month, which is between two and twenty times what a busy site does.

Fit

Five fits, and two of them do not give you the box.

Ask for 260×340 — a portrait box over a landscape photograph, which is the awkward case — and fit decides what happens to the difference. cover, contain and fill all hand back the box you asked for and disagree about what to sacrifice for it. inside and outside refuse to distort or crop anything, and hand back a different size instead.

The sample photograph at fit=cover
fit=cover

returns 260×340

The box, cropped to it.

The sample photograph at fit=contain
fit=contain

returns 260×340

The box, padded to it.

The sample photograph at fit=fill
fit=fill

returns 260×340

The box, stretched to it.

The sample photograph at fit=inside
fit=inside

returns 260×173

Smaller than the box.

The sample photograph at fit=outside
fit=outside

returns 510×340

Bigger than the box.

All five are live URLs against the same source, and all five are drawn at 40% of the pixels they came back as — so their sizes are comparable to each other rather than each one fitted to its own box, which would hide the only difference that matters between the last two.

Position

A centred crop is a face cut in half.

fit=cover throws away whatever hangs over the edge, and by default it throws it away symmetrically. position moves the anchor to one of nine places, which is usually the difference between a thumbnail you can ship and one you have to crop by hand. Three of the nine are below, cropping a 240×420 portrait out of a landscape photograph — the case where the parameter has the most to say. Multi-word values need encoding: position=left%20top.

gravity is the same idea under an older name, compass spellings only, and it is read only when position is absent. Reach for position in anything new.

A tall crop of the sample photograph, anchored left
position=left
A tall crop of the sample photograph, anchored center
the default
A tall crop of the sample photograph, anchored right
position=right

The URL

Every parameter there is.

Not the popular ones — all of them. Nine, plus the short spellings w and h, with the default beside each one so you can tell what happens when you leave it out. If a table below and the docs ever disagree, the docs are the ones kept in step with the code.

Size

Give one and the other follows the source aspect ratio. Give both and fit decides the rest.

widthdefault: The source width

A whole number of pixels

Alias: w. They are the same parameter, and the short form wins if you send both — w=400&width=800 renders 400. Send one.

heightdefault: The source height

A whole number of pixels

Alias: h, with the same rule — h beats height when both appear.

Shape

Only read when there is a size to act on — with neither dimension set, all four of these do nothing.

fitdefault: cover

cover · contain · fill · inside · outside

How the image is made to fit the box you asked for. The five below are the whole set.

positiondefault: center

center · top · right top · right · right bottom · bottom · left bottom · left · left top, and the compass spellings of the same nine

Where the crop is anchored when fit=cover has to throw part of the image away. Multi-word values need encoding: position=left%20top.

gravitydefault: center

The nine compass names only

A compatibility alias for position. position is read first and gravity only when it is absent — prefer position in new code.

backgrounddefault: Opaque black

Any CSS colour — `#ffffff`, `white`, `rgba(0,0,0,0.5)`

What fills the space when fit=contain leaves some. A colour the parser does not recognise fails the render with a 500, so encode the # as %23.

Format

You need none of these on an ordinary day. The default behaviour is the interesting part, below.

formatdefault: Negotiated from `Accept`, then the source format

avif · webp · jpeg · png

Pins the container. An explicit format always beats negotiation, so reach for it when a URL has to return the same bytes to every client. Anything else is a 400.

withoutEnlargementdefault: false

true · 1 · yes · false · 0 · no, or the bare parameter

Never scale past the source dimensions. Sending it with no value at all — ?withoutEnlargement — reads as true.

fastShrinkOnLoaddefault: false

true · 1 · yes · false · 0 · no, or the bare parameter

Take the JPEG decoder's shrink-on-load path on a large downscale. Faster and much easier on memory, at a little quality.

Format

The biggest saving is the one you did not ask for.

Resizing a photograph is usually the second-largest win available on it. The largest is the container: a modern encoder on the same pixels routinely beats the resize. Sizy reads the Accept header the browser already sends and picks, with no parameter from you.

Each format is stored as its own object and every response carries Vary: Accept, so nothing in between can hand an AVIF to something that cannot decode one. It is still one billable transform per URL — you are not charged again because a visitor turned up in a different browser.

Two sources are never converted at all. An animated GIF would lose every frame but the first, and rasterising an SVG is a decision rather than an optimisation.

the Accept header, in order of preference
image/avif,image/webp,*/*
AVIFChrome, Edge, Firefox, Safari 16.4+
image/webp,*/*
WebPOlder Safari, most of what is left
*/*
the source formatcurl, link previewers, anything not a browser

A client sending only */* gets the source format on purpose: plenty of things that are not browsers send that and cannot decode AVIF. Set ?format= when a URL has to return the same bytes to everyone.

Integration

There is nothing to integrate.

This is the part of the page where a product like this usually shows you an install command. There isn't one. A responsive image is the markup you already know with a different hostname in it, and if you want a helper, the helper is three lines and you should write it rather than depend on it.

index.html
<!-- One image, three sizes, no build step. -->
<img
  src="https://sizy.io/sizy/photos/beach.jpg?w=600"
  srcset="https://sizy.io/sizy/photos/beach.jpg?w=600   600w,
          https://sizy.io/sizy/photos/beach.jpg?w=1200 1200w,
          https://sizy.io/sizy/photos/beach.jpg?w=1800 1800w"
  sizes="(max-width: 640px) 100vw, 600px"
  alt="The beach at Zuma"
/>

<!-- A square thumbnail of the same file, anchored so the faces live. -->
<img
  src="https://sizy.io/sizy/photos/team.jpg?w=96&h=96&fit=cover&position=top"
  width="96" height="96" alt="The team"
/>

<!-- And in a stylesheet, because it is still only a URL. -->
<div style="background-image:
  url('https://sizy.io/sizy/photos/hero.jpg?w=1600&h=500')"></div>
lib/img.js
// The entire client library, if you want one.
const BUCKET = "https://sizy.io/sizy";

export const img = (path, opts = {}) =>
  `${BUCKET}/${path}?` + new URLSearchParams(opts);

img("photos/beach.jpg", { width: 400, height: 300 });
// https://sizy.io/sizy/photos/beach.jpg?width=400&height=300

img("photos/team.jpg", { w: 400, h: 300, position: "top" });
// A crop anchored to the top, so the faces survive it.

// There is no key on an image URL and no request to make from a server:
// a bucket is a public front for a source you already serve publicly.

What counts

What Sizy calls a transform.

One number is billed, so it is worth being exact about what increments it — including the two cases where you would reasonably expect it to and it does not.

No size, no resize

With neither width nor height present, fit, position, gravity and background have nothing to act on and the image comes back at its original dimensions.

The format is negotiated, not guessed

Sizy reads the Accept header the browser already sends: AVIF where it is offered, then WebP, then the source format. A client sending only */* gets the source — plenty of non-browser clients send that and cannot decode AVIF.

One URL is one transform, whatever the browser

Each format is stored as its own object and every response carries Vary: Accept, so no cache in between can hand an AVIF to something that cannot read it. You are still billed once for the URL, not once per browser your visitors use.

Unknown parameters do not make a new image

Anything outside the tables above is dropped before the image is identified, so ?utm_source=newsletter will not quietly render and bill a second copy of the same picture.

Except the ones meant for your origin

v, x-id and the X-Amz-* signing parameters are forwarded to your source so a bucket can front a presigned S3 URL. They are forwarded but not part of the identity, so a signature that changes every hour does not re-render anything.

The same URL is the same image, forever

An image is identified by its bucket, its path and the parameters above. The first request renders and stores it; every later one that month is answered from the stored copy. The URL never changes, so it is safe to hard-code, to cache and to put in an email.

Stored copies clear at the end of the month

A render is kept until the end of the calendar month it was made in, or seven days, whichever is longer. A URL you serve every month costs one transform a month — not one forever — and a URL nobody asks for again costs nothing after it clears.

1/mo

A URL you serve forever costs one transform a month, not one forever.

Rendered copies are cleared at the end of the calendar month they were made in — or after seven days, whichever is longer, so an image rendered on the 30th is not swept away the next morning. The next request re-renders it, and that is the one more transform. The upshot is that your bill tracks the images people are actually looking at this month, and a page you retired in March stops costing anything in April without you doing a thing.

Price

$0.40 a thousand, and nothing else on the invoice.

No monthly fee, no seats, no minimum, no storage line and no bandwidth line. The first 5,000 transforms every month are free on every account, including accounts that pay. Below is the same month priced on all six, which is harder than it sounds — every one of these bills on a different axis.

$6.00a month on Sizy, all in — and the deliveries above are not part of it
Sizy$6

Pay as you go$0.40 per 1,000 transforms after 5,000 free — delivery is not billed at any volume

Cloudflare Images$8

Pay as you go$0.50 per 1,000 unique transformations after 5,000 free

Vercel$20

Pro$20/mo including $20 of credit, then $0.05 per 1,000 transformations plus 10 cache units an image

imgix$49

Starter195.6 credits — one per 500 renders, one per GB delivered, two per GB stored

ImageKit$65

Lite152.6GB delivered and 1.5GB stored — transformations are not billed separately

Cloudinary$99

Plus174.1 credits — one credit is 1,000 transformations, 1GB stored or 1GB delivered

Rates read from each vendor's published pricing on 2026-09-03, always at the cheapest plan that covers the usage rather than the one they would sell you. Storage is modelled at one stored object per transform, because three of these bill for it and leaving it out would flatter them. Cloudflare Images · Cloudinary · imgix · ImageKit · Vercel

The shape this is best at: an agency running twenty client sites — 20,000 images and 2 million views a month — is $6 here against $99 on Cloudinary. Rates checked 2026-09-03; the full working is on the pricing page.

When Sizy is the wrong choice

  • A very large catalogue served lightly. ImageKit meters bandwidth, not transforms. A quarter of a million distinct images with modest traffic against them is us charging per image for something they charge per gigabyte for — and the gigabytes are small.

  • You are already paying for Vercel. Their image optimisation comes with the hosting rather than being something you buy. The comparison includes that $20 because there is no way to get the feature without it; if it is already on your bill, subtract it. The images themselves cost very little, and that is hard to argue with.

  • You fit inside somebody's free tier. Cloudinary and ImageKit are both more generous than us right at the bottom. If you fit and expect to stay there, take it.

  • You need more than resizing. No video, no asset manager, no AI editing, no custom domain, no SLA, no procurement paperwork.

Responses

The happy path is a redirect.

Sizy does not stream your image through itself on every request. It answers a 307 pointing at a signed URL for the rendered object in its own storage, good for fifteen minutes — which is why delivery can be free, and why the thing you should never do is save where the redirect went. Keep pointing at the sizy.io URL; browsers and <img> follow it for you.

When the fetch from your origin fails, Sizy hands you back your origin's own status rather than inventing one — a missing file at your source is a 404 here, and a 403 stays a 403.

307

Found

The normal answer. Sizy redirects to a signed URL for the rendered image in its own storage, good for fifteen minutes. Follow the redirect rather than saving where it points — browsers and <img> do that for you. Keep pointing at the sizy.io URL.

400

Invalid input parameters

A parameter was there but not accepted: a width that is not a number, a fit outside the five, a format outside the four. The body is plain text naming the parameter.

404

Bucket not found

No active bucket by that name — or a name the site reserves for itself.

your origin's

The source did not hand over the image

When the fetch from your source fails, Sizy answers with the status your origin gave. A missing file at your origin is a 404 here; a 403 stays a 403.

500

Error processing image

The source answered but the bytes could not be rendered — not an image Sharp understands, or a background that is not a colour.

Fine print

What it will not do.

Better to read this now than to find out after you have rewritten your image tags.

There is no quality parameter

Encoder quality is fixed. You choose the container with format and not how hard it is squeezed — which is the right default far more often than it is a problem, but it is a fixed default.

No rotate, blur, sharpen or watermark

The three tables above are the complete API. Sizy resizes, crops and re-encodes; anything else is a job for the thing that made the image.

No custom domains

Images are served from sizy.io. If the hostname on your image URLs has to be yours, this is the wrong product today.

Animated GIF and SVG pass through

Converting an animated GIF would lose every frame but the first, and rasterising an SVG is a decision rather than an optimisation. Neither is ever converted.

A bucket is public

There is no API key on an image URL, because a bucket is a public front for a source you already serve publicly. Private originals want a presigned source URL, not a private bucket.

A bucket name is gone for good

Names are unique across all of Sizy, must be three characters or more, and are not released when a bucket is deleted. Keep them to thirty characters — longer ones save, but the image route will not resolve them.

And these names belong to the site itself, so they always 404 as a bucket:

assetsavatarsimagesfilesfavicon.ico.well-knowndevpublic

Point it at what you already have.

A bucket takes a name and an origin URL, and it is live the moment you save it. The first 5,000 transforms each month are free and no card is needed to find out whether this is what you wanted.