728x90 leaderboard ad image: GPT Image 2.5 can't, Nano Banana 2 can
A 728x90 ad is 8.09:1. GPT Image 2.5 caps custom sizes at 3:1, but the Sume catalog lists 8:1 for google/nano-banana-2. Check the row, request 8:1, crop 1.1%.

For a 728x90 leaderboard ad image, use google/nano-banana-2 with aspect_ratio: "8:1" and trim the leftover 1.1 percent in Pillow. GPT Image 2.5 cannot do this shape: 728 / 90 = 8.09, and its custom sizes stop at 3:1.
The Sume image catalog is per model, so the way to be sure is to read it. The script below checks the catalog row for 8:1 before it spends anything.
Which ratio each model offers
The Sume catalog lists 8:1 and 4:1, plus their tall twins 1:8 and 1:4, on Nano Banana 2. Among the models with a fixed aspect_ratio list, none of the others goes past 3:1 (Ideogram) or 21:9 (Nano Banana Pro). That makes 8:1 a model decision, not a size decision.
| Option | Widest shape | 728x90 needs |
|---|---|---|
| GPT Image 2.5 custom size | 3:1 (OpenAI guide) | 8.09:1, too wide |
| google/nano-banana-2 aspect_ratio | 8:1 listed in the Sume catalog | 8.09:1, close |
| Crop from the 8:1 render | 8.000 / 8.089 = 98.9% of height kept | 1.1% trimmed |
Code
The guard reads supported_parameters.aspect_ratio.values from GET /v1/images/models, the same descriptor Sume uses to reject unsupported values. If the row ever drops 8:1, the script fails before the paid call.
import os, requests
from io import BytesIO
from PIL import Image, ImageOps
H = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
MODEL = "google/nano-banana-2"
rows = requests.get("https://api.sume.com/v1/images/models", headers=H, timeout=30).json()["data"]
row = next(m for m in rows if m["id"] == MODEL)
assert "8:1" in row["supported_parameters"]["aspect_ratio"]["values"]
r = requests.post("https://api.sume.com/v1/images", headers=H, timeout=60,
json={"model": MODEL, "aspect_ratio": "8:1",
"prompt": "Wide panorama of a city skyline at dusk, plain sky on the left"})
if r.status_code != 200:
raise SystemExit(f"{r.status_code}: {r.text[:300]}")
img = Image.open(BytesIO(requests.get(r.json()["data"][0]["url"], timeout=60).content))
ImageOps.fit(img.convert("RGB"), (728, 90), Image.LANCZOS).save("ad-728x90.png")Cost and caveats
This post gives no price because Nano Banana 2 is priced per image, not per token, and the line belongs on its endpoint record. Read it from GET /v1/images/models/google/nano-banana-2/endpoints, where the pricing array carries the billed USD amount.
Very thin banners are hard on the picture. Keep the subject small, give it a flat band of background, and add the logo and offer text in code. A 90-pixel-tall file leaves no room for generated lettering.
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.
Before you ship the file
A short checklist saves a rejected upload at the end of a campaign build.
- Open the resized file at 100 percent and read every word of live text.
- Check the file type your ad network expects. The API returns the format you request through
output_format(png,jpegorwebp, catalog-gated), so confirm it on the model row. - Keep the 1x and 2x files side by side in your asset folder, named with the pixel size.
- Store the Sume
data[].urlresult in your own storage. The Sume docs tell integrations to keep the Sume URL, not any provider URL.
Sources
Related posts
More in Use cases
- A 90-second 21:9 trailer: three 30 s Seedance 2.5 jobs, $52.46
Kling 4.0 tops out at 30 s, so a trailer is several clips. On Sume, three 30 s Seedance 2.5 jobs at 720p 21:9 are $52.26, plus $0.20 for the Timeline join.
- 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.
- A/B test two AI music beds on one ad: cost and the Timeline steps
Make two beds for one ad at $0.125 each, render both cuts on Timeline at $0.10 per minute, and compare. A cost table for a 30-second ad.
- A4 poster at 300 dpi: 2480x3508 is over the GPT Image 2.5 pixel cap
A4 at 300 dpi is 2480x3508, 8,699,840 pixels, above the 8,294,400 cap. Render 2400x3392 on GPT Image 2.5, then scale up 3.4% in Pillow and tag it 300 dpi.
Written by Sume