970x250 billboard ad image: render 1920x640, crop 23% of height

A 970x250 ad is 3.88:1, wider than the 3:1 limit on GPT Image 2.5. Render 1920x640 (3:1), scale to 970 wide, and crop the height. Table of checks and prices.

5 min readSume
All posts

A 970x250 billboard ad is 3.88:1, which is wider than the 3:1 cap on GPT Image 2.5 custom sizes, so you cannot ask for it directly. Render 1920x640 (exactly 3:1), scale it to 970 wide, and crop the height from 323 pixels to 250.

The crop removes 73 of 323 rows, about 22.6 percent of the height. Plan the picture for that before you pay for it: the subject belongs in the middle band, with sky and foreground you can lose.

Numbers

1920x640 sits right on the ratio limit and well inside the pixel range, so it passes every rule the OpenAI guide lists for custom sizes.

1920x640 against the GPT Image 2.5 custom-size rules (read 2026-10-05)
CheckValueResult
Both edges multiples of 161920 = 16 x 120, 640 = 16 x 40pass
Longest edge at most 38401920pass
Aspect ratio at most 3:13.000:1pass
Pixels 655,360 to 8,294,4001,228,800pass

Code

The same fit call does both steps. It scales 1920x640 to 970x323, then trims the overflow equally from the top and bottom.

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": "Wide scene of a mountain lake at dawn, the main subject in the center band, calm empty sky above and water below",
    "image_size": {"width": 1920, "height": 640},
    "quality": "high",
})
img = Image.open(BytesIO(requests.get(url, timeout=60).content)).convert("RGB")
ImageOps.fit(img, (970, 250), Image.LANCZOS).save("ad-970x250.png")

What one render costs

These are output-only prices from the Flare token rate. Sume bills list times 1.25. A wide master gives you room for a 2x file too: crop 1940x500 from the same render and the shape still matches.

Output-only price per 1920x640 image (read 2026-10-05)
QualityProvider listSume at list x 1.25
low$0.0019$0.0024
medium$0.0046$0.0058
high$0.0186$0.0232

Writing the prompt for the crop

Tell the model the picture will be cropped to a thin band. Keep faces and products between 30 and 70 percent of the height, and leave copy space at one end of the band for the headline you add in code.

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