Skip to content
Chapter 4 of 7 · Image inputsTile-based image pricing

17 min read

Tile-based image pricing

On this page

Chapter 2 described three ways providers price an image and warned that one of them rounds. Tile-based billing is that one: the provider scales an upload to fit a bounding box, normalises its short side, cuts the result into fixed 512-pixel squares, and charges a base amount plus a fixed price per square. Because a partial tile is charged as a whole one, the token count moves in steps. It is flat across a wide range of sizes and then jumps.

That behaviour breaks the intuition every resize decision rests on — that sending fewer pixels costs less money in something like proportion. Under tiling the relationship between pixels and price is a staircase, the steps are invisible from outside until somebody measures them, and a resize landing inside the step it started in is pure loss: degraded input, identical invoice.

Where the pixels go and the money doesn't

The rounding is worth taking apart before any arithmetic rests on it, because it is not a pricing policy somebody chose. A model does not read a rectangle of pixels; it reads a sequence of discrete units, the same kind of sequence it gets from text. The provider converts one into the other by cutting the rectangle into crops of a fixed size and turning each crop into a fixed number of units, so the length of the sequence depends on how many crops the image was cut into and on nothing else about the picture.

A crop is a fixed size, and an image's width is under no obligation to be a whole number of crops. Where it is not, the leftover strip still has to be completed to a full crop before it can be processed at all, so the remainder is padded out and fed through like any other crop. A crop that is mostly padding produces exactly as many units as a full one, because the cost is the crop and not the content inside it. That is the causal step underneath the ceil in every calculation below, and it is why the staircase follows from how an image is read rather than from a decision about how it is billed.

Take a client where every uploaded screenshot is downscaled from 1024×1024 to 800×800 before sending, a 39% reduction in pixels, adopted to reduce cost. The tiled scheme runs three steps in order: fit the image inside 2048×2048, scale it so the short side is 768, then count 512-pixel tiles across and down. Run 1024×1024 through them. The first step does nothing, since both edges are already inside 2048 and nothing needs shrinking to fit. The second multiplies both edges by 768 over the short side, and the short side is 1024, so the factor is three quarters and the image becomes 768×768. The third divides 768 by 512 in each direction, which is one and a half, and one and a half crops is two crops, so the grid is two by two and the count is four.

Now run 800×800 through the same three steps. The first does nothing again, and the second multiplies both edges by 768 over 800, a factor a little over nineteen twentieths, landing on the identical 768×768 the larger image reached by multiplying by three quarters. Two different multiplications applied to two different inputs arrive at the same output, because that step targets a fixed short side rather than a fixed reduction. From there the third step cannot tell the images apart: ceil(768/512) = 2 in each direction either way, four tiles either way. That collision is the whole reason the money does not move.

Turning tiles into tokens is a base amount plus a fixed price per tile, and both figures can be read off the table in the next section rather than taken on trust. Four tiles cost 765 tokens and six cost 1,105, a difference of 340 tokens for two tiles, so one tile is a hundred and seventy tokens. Four of them come to six hundred and eighty, and 765 less six hundred and eighty leaves eighty-five, which is the base — a per-image charge levied whether the image is four tiles or twelve. Both sizes are four tiles, so both cost eighty-five plus four times a hundred and seventy, which is 765 either way. Even under the cruder mental model — skip normalisation, tile the raw dimensions — the answer is the same, because ceil(1024/512) and ceil(800/512) are both 2.

The belief the resize was built on is that fewer pixels means less money, and it is tempting because on a different billing family it is exactly true: where a provider charges by area, a 39% cut in pixels is close to a 39% cut in the image line. Nothing on the invoice tells you which family you are on, since both schemes take the same upload, return a single number, and print it in the same place. Establishing the family is the first move rather than a detail, because the arithmetic above is wrong on the other one.

The quality story is different, because quality does not round. The client's resize took the screenshot from 1024 to 800 before upload; the provider's normalisation then took it from 800 to 768. Without the client resize the provider would have taken 1024 straight to 768. So the pixels reaching the model went through two resamplings instead of one.

Two resamplings are worse than one for a reason that has nothing to do with the dimensions they end on. Resampling replaces a group of input pixels with an average, and an average is a lossy summary: two neighbouring pixels that differed before it are represented afterwards by a single value that no longer records that they differed. The second resampling then takes averages of those averages. It cannot restore a distinction the first one removed, because that distinction is no longer present in its input, so it begins from strictly less than the provider's single step would have begun from. One path forms its averages from the original 1024 pixels; the other forms them from a summary.

What makes this hard to see is that the nominal size at the end is identical. A digit that had roughly 11 pixels of cap height at 1024 arrives with the same 11 pixels of nominal height on either path, and only the edges differ: the boundary between ink and paper has been averaged twice on the two-step path, and each averaging spreads it a little further. Around the 10-pixel threshold from chapter 3 — below which extraction stops being reading and becomes inference — that softening is exactly what turns a legible character into a guessed one. On real traffic this shows up as an error code read as ERR_5188 or ERR_S183 when the screenshot says ERR_5183, on roughly one screenshot in nine, alongside an image line on the invoice that did not move at all. The upload does get smaller, which buys bandwidth and a little latency; the number billed for the image is what stays put.

The staircase

The step function is easiest to see by holding one thing fixed. With the short side normalised to 768, every image occupies exactly two tile rows, so the tile count is decided entirely by how wide the image is relative to its height — by its aspect ratio, not its size.

Two rows is the same one-and-a-half-crops calculation as before, and it is fixed for every row of the table below: the height after normalisation is always 768, always one and a half crops, always rounded up to two. Everything that varies between rows is horizontal. The normalised width is 768 multiplied by the aspect ratio, and the number of columns is that width divided by 512, rounded up, so the boundaries sit at exact multiples of 512 — at or below 1024 is two columns, above 1024 up to 1536 is three, above that four. That is what makes the table predictable instead of something to memorise.

Aspect ratioAfter normalisationTilesTokens
1:1768 × 7682 × 2 = 4765
5:4960 × 7682 × 2 = 4765
4:3 (1.333)1024 × 7682 × 2 = 4765
1.341029 × 7683 × 2 = 61,105
3:21152 × 7683 × 2 = 61,105
16:91365 × 7683 × 2 = 61,105
2:11536 × 7683 × 2 = 61,105
2.01:11544 × 7684 × 2 = 81,445

Four of those rows are worth working, because they sit either side of the two boundaries. A 4:3 image normalises to 768 times four thirds, which is 1024, and 1024 divided by 512 is two with nothing left over: two columns, two rows, four tiles. The row below it normalises to 1029, which is two whole crops of 512 with five pixels left over, and five pixels cannot be processed as five pixels — they are padded out to a third crop and charged as one. The pattern repeats one boundary along, where 2:1 normalises to 1536, exactly three crops, and 2.01:1 normalises to 1544, three crops and eight pixels. Those eight pixels buy a fourth column, and a fourth column is a fourth pair of tiles.

The first two of those four are the whole lesson. Going from 1:1 all the way to 4:3 — a 33% increase in pixels, the width growing from 768 to 1024 while the height holds — costs nothing. Going from 4:3 to 1.34 — a 0.5% increase, five pixels of width on top of 1024 — costs 340 tokens, which against 765 is a 44% jump. Cost is flat across wide plateaus and then jumps at boundaries you cannot see from the outside until you have measured them.

The other consequence is that absolute size does not appear anywhere in this scheme, and the normalisation step is the reason. It sets the short side to a constant, so whatever the upload's dimensions were, they are gone before anything is counted, and the only fact about the original that survives into the tile count is the ratio between its edges. A 768×768 thumbnail and a 4000×4000 photograph both normalise to 768×768 and both cost 765 tokens, because by the time the counting happens they are the same image. Anyone shrinking square images to save money is shrinking them for free, in the bad sense: no saving, and every pixel below 768 on the short side is a pixel the model would have had. That holds for squares at or above 768 on a side, where the normalisation has something to scale down; a smaller square is already under the target, and what a provider does with it is a question for the sweep below rather than for the table.

Measure the boundaries rather than trusting the table

The published rules describe intent; the billed number is what you pay. Sweeping the counting endpoint over a grid of synthetic images gives you the real staircase for the specific model you use, including any minimum, any rounding you guessed wrong, and any behaviour the reference did not mention.

python
from PIL import Image
import io, base64, itertools

def solid(w: int, h: int) -> dict:
    buf = io.BytesIO()
    Image.new("RGB", (w, h), (200, 200, 200)).save(buf, format="PNG")
    return {"type": "image", "source": {"type": "base64", "media_type": "image/png",
            "data": base64.standard_b64encode(buf.getvalue()).decode()}}

def image_tokens(w: int, h: int) -> int:
    with_img = client.messages.count_tokens(
        model=MODEL, messages=[{"role": "user", "content": [solid(w, h), {"type": "text", "text": "x"}]}]
    ).input_tokens
    without = client.messages.count_tokens(
        model=MODEL, messages=[{"role": "user", "content": [{"type": "text", "text": "x"}]}]
    ).input_tokens
    return with_img - without

widths = [512, 640, 768, 800, 900, 1000, 1024, 1030, 1152, 1280, 1536, 1544, 2048]
prev = None
for w in widths:
    t = image_tokens(w, 768)
    mark = "  <-- boundary" if prev is not None and t != prev else ""
    print(f"{w:>5} x 768   {t:>5} tokens{mark}")
    prev = t

Subtracting a baseline request is what isolates the image, and the reason is that the endpoint reports a whole request rather than a part of one. A request carries the text alongside the image, plus the scaffolding marking where a message begins, who is speaking and where one content block ends, and all of that occupies units in the same sequence the image occupies. An absolute number is therefore the image plus a constant, and the constant is what makes it dangerous rather than merely inaccurate: it does not vary as you sweep the widths, so it reads as a floor the image cost never drops below, which by eye is indistinguishable from a per-image minimum the provider does not charge.

Run the sweep once per model you use, check it into the repository next to the pricing code, and rerun it when you change model — the boundaries are a property of the model version, and chapter 2's warning about a raised resolution cap applies here with interest, because a model accepting a 2576-pixel long edge has a longer staircase with more steps on it.

Once you have the boundaries, resizing becomes a different operation. You stop picking round numbers and start picking the largest dimensions inside the cheaper step. If your screenshots are 1400×900 and you want to drop from six tiles to four, the target is not "80% of the original"; it is the largest size whose normalised width lands at or below 1024, which for a 900-pixel-tall source means cropping or padding toward 4:3 rather than scaling uniformly. A resize that lands you at 1030×768 has bought you the full price of the higher step for one pixel of width.

The belief being replaced there is that a resize percentage is the right unit of decision. It is tempting because a percentage is the control an image library actually exposes, and because every other cost conversation is conducted in percentages. Under tiling the unit is a boundary crossing, and a percentage tells you nothing about whether one happened: the same reduction moves one image across a boundary and leaves the next exactly where it was, decided entirely by where each started.

The staircase is also the right way to look at your existing traffic before you change anything. Take a week of uploads, compute the tile count for each with the function you just validated, and histogram it. What usually comes back is that image spend is concentrated in one or two steps, with a long tail sitting one step above them for a reason nobody chose — a camera library that emits 16:9 by default, an export that pads to a widescreen canvas, a screenshot tool that includes the browser chrome and pushes an otherwise 4:3 capture over the boundary. Those are the cheap wins, because moving an image down a step by removing padding costs nothing in the content the model needs to read. That is a different operation from downscaling, and the histogram is what tells them apart: downscaling reduces the resolution of everything, while re-shaping moves specific images across a boundary and leaves their legible content at full size.

Treating padding and content as the same pixels is the belief that hides those wins, and it survives because to the biller they are the same pixels: a crop of blank canvas produces exactly as many units as a crop dense with text. To the model they are not.

Multiple images in one request behave additively, with no discount for sending twenty pages together rather than twenty times one page, which means the staircase applies per image and a document pipeline pays the boundary cost once per page. On a 24-page document where every page sits one pixel over a boundary, that is 340 tokens × 24 pages of pure waste on every single request — the kind of thing that is invisible per call and obvious per month. Waste is the right word only where the pixels carrying each page over the boundary are margin or scan border; where a page is genuinely that wide, the higher step is buying something.

Where a staircase does not apply

Tiling is not universal. Area-based providers have no staircase, and on those a resize really does reduce cost roughly in proportion to the pixels it removes, right down to any stated minimum — the reasoning above would be wrong applied to them, which is why the first move is always to establish which billing family you are on before optimising anything.

Nor does any of this make resizing a mistake. Crossing a boundary on high-volume traffic is real money, and an image class whose fields all survive at four tiles instead of eight is a genuine halving of the image line. The point of the staircase is that it makes the saving predictable in advance: you can compute, before touching a single upload, whether a proposed resize moves any images across a boundary at all, and if it does not, the proposal is offering degradation at no discount.

Tile-based pricing rewards a specific and slightly unusual discipline. Instead of choosing a resize percentage, you measure where the steps fall for your model, look at where your own traffic sits relative to them, and then move images across boundaries — often by cropping padding or fixing an aspect ratio rather than by shrinking anything. The images that end up smaller are the ones where a boundary was genuinely in reach, and everything else keeps every pixel it had, because those pixels were free.

Resolution is not the only control over image cost, though. There is a much coarser one available on some providers, a single parameter that skips the staircase entirely and drops an image to a small flat charge regardless of what you send. It is tempting for exactly that reason, and its failure mode is not a misread character.

Image inputs — read the current version at https://daviddominguez.dev/learn/en/image-inputs/tile-based-image-pricing

Back to top