300x250 display ad image: generate 1440x1200, then resize by 4.8

A 300x250 ad is 6:5, so GPT Image 2.5 can render it at 1440x1200 (legal custom size) and Pillow scales it down by exactly 4.8. Size checks and price included.

5 min readSume
All posts

To make a 300x250 display ad image with an API, ask GPT Image 2.5 for a 1440x1200 custom size and shrink the result to 300x250 in Pillow. 300x250 is 6:5, so 1440x1200 is the same shape, and 1440 / 300 = 4.8 and 1200 / 250 = 4.8 mean the downscale needs no crop.

The reason not to request 300x250 directly is the size rule. The OpenAI guide says custom sizes need both edges to be multiples of 16, with a longest edge of 3840, a ratio of at most 3:1, and 655,360 to 8,294,400 total pixels. 300x250 is only 75,000 pixels and neither edge is a multiple of 16, so it fails two of the four checks. Sume's Image API takes the same custom pixels on image_size.

Why 1440x1200 is the clean pick

Other 6:5 boxes exist, but 1440x1200 is the smallest round one that passes every check and divides evenly into the ad size. A larger master also lets you cut a sharper 2x file (600x500) from the same render.

1440x1200 against the GPT Image 2.5 custom-size rules (read 2026-10-05)
CheckValueResult
Both edges multiples of 161440 = 16 x 90, 1200 = 16 x 75pass
Longest edge at most 38401440pass
Aspect ratio at most 3:11.200:1pass
Pixels 655,360 to 8,294,4001,728,000pass

Request and resize

Send image_size as an object. It has priority over aspect_ratio, so send only one of them. The route waits up to 30 seconds and returns 200 with data[].url. If a slow configuration returns 202, the script below stops and prints the job envelope so you can read status_url.

import os, requests
H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}

def generate(body):
    r = requests.post("https://api.sume.com/v1/images", headers=H, json=body, timeout=60)
    if r.status_code != 200:  # 202 = still running, read data.status_url
        raise SystemExit(f"{r.status_code}: {r.text[:300]}")
    return r.json()["data"][0]["url"]
from io import BytesIO
from PIL import Image, ImageOps

url = generate({
    "model": "openai/gpt-image-2.5",
    "prompt": "Clean studio shot of a running shoe on a pale blue ground, product centered, empty space on the left third",
    "image_size": {"width": 1440, "height": 1200},
    "quality": "high",
})
img = Image.open(BytesIO(requests.get(url, timeout=60).content)).convert("RGB")
ImageOps.fit(img, (300, 250), Image.LANCZOS).save("ad-300x250.png")

What it costs

The fal Flare page prices GPT Image 2.5 by output tokens, and the Sume docs name the rate as $30 per million output image tokens, before input tokens and Sume pricing. Applying the token formula to 1440x1200 gives the list price below. Sume bills provider list times 1.25. Text input at $5 per million tokens adds a fraction of a cent for a short prompt, so these are output-only figures.

The default quality on Sume is high when you leave the field out. For ad concepts, run low first and spend the high price only on the layout you keep.

Output-only price per 1440x1200 image (read 2026-10-05)
QualityProvider listSume at list x 1.25
low$0.0058$0.0073
medium$0.0134$0.0168
high$0.0537$0.0671

Keep the ad safe to crop

Because the shape is identical, nothing is lost in the resize. Still, put the product in the right two thirds and leave the left third empty in the prompt, then add the headline and logo in code. Rendering the copy yourself keeps it sharp at 300 pixels wide, where generated lettering turns to mush.

Draft cheap, finish once

A display set usually needs several concepts before one is approved. Request the first round at quality: "low", pick the layout, then repeat only the winner at high. Quality is a per-request field on both ChatGPT Image 2.5 variants (openai/gpt-image-2.5 and openai/gpt-image-2.5-sunburst), and the accepted values are auto, low, medium, high, xhigh and max. Avoid auto: Sume reserves the max price for it, so set the tier yourself.

Flare is the faster variant and Sunburst is tuned for edit precision, according to the fal pages. Both share the same token rates, so for an ad pass you can pick on behavior, not on price. Auto routing on Sume (sume/auto) uses Flare.

If the call returns 202 instead of 200

POST /v1/images waits up to 30 seconds and answers 200 with the images when the job finishes in time. When it does not, the route answers 202 with the standard job envelope, and the images come from GET /v1/jobs/{id}/result. Slow settings such as 4K, high quality and large n are the likeliest to fall back. A 1-2 megapixel ad master at high quality usually stays in the wait budget, but the code should branch on the status code, not the body shape.

For a batch of ad sizes, send each request with mode: "async" and read the results afterwards, or add a webhook_url with mode: "webhook". A failed synchronous job returns 502 with an error code and a next_action, and Sume does not bill failed generations.

Sources

Related posts

More in Use cases

All Use cases posts

Written by Sume