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%.

5 min readSume
All posts

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.

Wide-shape limits for a 728x90 target, Sume catalog and OpenAI guide (read 2026-10-05)
OptionWidest shape728x90 needs
GPT Image 2.5 custom size3:1 (OpenAI guide)8.09:1, too wide
google/nano-banana-2 aspect_ratio8:1 listed in the Sume catalog8.09:1, close
Crop from the 8:1 render8.000 / 8.089 = 98.9% of height kept1.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, jpeg or webp, 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[].url result 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

All Use cases posts

Written by Sume