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.

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.
| Check | Value | Result |
|---|---|---|
| Both edges multiples of 16 | 1440 = 16 x 90, 1200 = 16 x 75 | pass |
| Longest edge at most 3840 | 1440 | pass |
| Aspect ratio at most 3:1 | 1.200:1 | pass |
| Pixels 655,360 to 8,294,400 | 1,728,000 | pass |
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.
| Quality | Provider list | Sume 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
- Document to video with Wan 3.0: what Sume accepts instead of a PDF
Alibaba's Wan 3.0 takes documents and webpages as references. Sume's catalog documents image, video and audio references, so convert pages to images first.
- Does YouTube's AI label hurt reach or monetization? What it says
YouTube states a disclosure label alone does not change recommendations or monetization eligibility. What it covers, what it does not, and a workflow.
- Do resizing or color correction need Meta's AI info label?
Meta says minor edits such as resizing or color correction do not need an AI label, and not every ad shows one yet. What that means for a generated ad clip.
- Does YouTube's Shorts update demonetize AI videos?
The Oct 2026 change touches recommendations, not the Partner Program rules. How reach and monetization differ for AI Shorts, with YouTube's own wording.
Written by Sume