160x600 skyscraper ad image: 3.75:1 is past GPT Image 2.5's 3:1 cap
A 160x600 ad is 1:3.75, taller than the 3:1 limit on GPT Image 2.5 custom sizes. Render 576x1728 (1:3) and crop 20% of the width in Pillow. Math included.

You cannot generate a 160x600 skyscraper ad image directly with GPT Image 2.5, because 600 / 160 = 3.75 and the model's custom sizes stop at a 3:1 ratio. The workable path is to render 576x1728 (exactly 1:3) and let Pillow crop the sides down to 160x600.
This is the one standard ad shape that breaks the rule outright. 300x600 and 300x250 fit comfortably, but 160x600 is thinner than the model allows.
The crop math
Scale 576x1728 by 1 / 2.88 and you get 200x600. The target is 160 wide, so 40 pixels go, 20 per side. That is 20 percent of the width, and none of the height. Because the shape change is a pure side crop, you control it with one sentence in the prompt: keep the subject in the middle 80 percent of the width.
| Check | 160x600 direct | 576x1728 render |
|---|---|---|
| Both edges multiples of 16 | no (160 yes, 600 no) | yes (36 x 16, 108 x 16) |
| Aspect ratio at most 3:1 | 3.75:1, fail | 3.000:1, pass |
| Pixels 655,360 to 8,294,400 | 96,000, fail | 995,328, pass |
| Longest edge at most 3840 | 600, pass | 1728, pass |
Code
ImageOps.fit scales to cover the target and crops the overflow from the center, which is the 20 percent side trim above. The helper stops on any non-200 status so a 202 never gets saved as an image.
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": "Tall poster of a single red bicycle against a plain wall, bike centered, plenty of margin left and right",
"image_size": {"width": 576, "height": 1728},
"quality": "high",
})
img = Image.open(BytesIO(requests.get(url, timeout=60).content)).convert("RGB")
ImageOps.fit(img, (160, 600), Image.LANCZOS).save("ad-160x600.png")Price
The output-token rate on the fal Flare page is the base for the figures below. Sume bills provider list times 1.25, before the small input-token charge for the prompt.
| Quality | Provider list | Sume at list x 1.25 |
|---|---|---|
| low | $0.0018 | $0.0022 |
| medium | $0.0043 | $0.0054 |
| high | $0.0173 | $0.0216 |
When to pick another model
If the crop costs you too much, check the catalog before you commit. Each model lists its own aspect_ratio values at GET /v1/images/models, and Sume rejects anything a row does not list with 400 unsupported_parameter. A model that lists 1:8 gives you a thinner starting shape than 1:3.
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
- 3-minute holiday gift guide Short from six Wan 3.0 clips: $23.06
YouTube Shorts can run 3 minutes. Six 30-second Wan 3.0 clips at 720p ($22.50), voice ($0.13), music ($0.13) and Timeline ($0.30) cost $23.06.
- 3-second logo sting with Omni: end frame as the lockup, cost on Sume
Make a 3-second logo reveal: first frame a texture, last frame your logo lockup. Omni 3 s is $0.12 at 360p and $0.57 at 1080p on Sume. Request and checks.
- A 30-second ad from three 10 s MiniMax H3 clips: cost and joins
H3 caps at 15 s per clip. Three 10 s clips make 30 s for $2.25 at 768p on H3, $3.00 on H3 Max, $6.00 at 1080p. Use end frames to keep the shots linked.
- 30-second holiday UGC ad with a music bed: a $7.59 end-to-end receipt
Avatar Plus 30 s $7.35, voice detach $0.01, Lyria bed $0.13, Timeline mix $0.10. One finished UGC ad with a ducked music bed costs $7.59 on Sume.
Written by Sume