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.
An A4 poster at 300 dpi is 2480x3508 pixels, and that is 8,699,840 pixels, which is over the 8,294,400-pixel ceiling the OpenAI guide gives for GPT Image 2.5 custom sizes. The fix is to render 2400x3392 (8,140,800 pixels, same shape within 0.1 percent) and scale it up by about 3.4 percent in Pillow.
The arithmetic is simple. A4 is 210 x 297 mm, or 8.268 x 11.693 inches. At 300 dpi that is 2480 x 3508 pixels. A 3.4 percent enlargement from a clean render is a small step compared with printing a 1024-pixel image at A4.
Why 2400x3392
Both edges must be multiples of 16, so 2480 and 3508 are out (3508 / 16 = 219.25). 2400x3392 is a legal box close to the A4 ratio of 1.4142: both edges are multiples of 16, it stays under the pixel cap, and its ratio of 1.4133 is within a tenth of a percent.
| Check | 2480x3508 direct | 2400x3392 render |
|---|---|---|
| Both edges multiples of 16 | no (3508 / 16 = 219.25) | yes (150 x 16, 212 x 16) |
| Longest edge at most 3840 | 3508, pass | 3392, pass |
| Aspect ratio at most 3:1 | 1.414:1, pass | 1.413:1, pass |
| Pixels 655,360 to 8,294,400 | 8,699,840, fail | 8,140,800, pass |
Code
The fit call enlarges the render to cover 2480x3508 and trims a fraction of a pixel row. The saved PNG carries a 300 dpi tag so print software reads the intended size.
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": "Minimalist concert poster, a single lit stage microphone in an empty hall, large calm area at the top for a title",
"image_size": {"width": 2400, "height": 3392},
"quality": "high",
})
img = Image.open(BytesIO(requests.get(url, timeout=60).content)).convert("RGB")
img = ImageOps.fit(img, (2480, 3508), Image.LANCZOS)
img.save("poster-a4.png", dpi=(300, 300))Price of a print-size render
A 2400x3392 image has about eight times the pixels of a 1024 square, but at high quality its output-token price is only about 2.4 times the square's (about $0.124 against $0.053 at list). Using the token formula behind the fal Flare price page, the output-only list and Sume prices are below. Sume bills list times 1.25, before the input-token charge for the prompt.
Draft at low, which costs a small fraction of high, and reserve the print-size high call for the approved layout.
| Quality | Provider list | Sume at list x 1.25 |
|---|---|---|
| low | $0.0134 | $0.0168 |
| medium | $0.0311 | $0.0388 |
| high | $0.1241 | $0.1552 |
| xhigh | $0.2191 | $0.2738 |
Set the text in code
At print size, misspelled or warped lettering is expensive to discover. Ask the model for the artwork with a clean empty area, then set the title, date and venue in your layout tool or in Pillow. The OpenAI guide itself notes that text rendering and composition control remain challenging areas, so the safer production path is to keep exact copy out of the generated pixels.
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
- ACCC Black Friday sweep: match the clip's end date to the sale end
ACCC Black Friday sweeps target limited-time claims and countdown timers that miss the real sale end. Burn the end date from one value. Read 2026-10-05.
- Add a logo to a product image: Ideogram 4.5 with two input references
Send the product photo first and the logo second in input_references on Sume, and say which is which in the prompt. Order matters: the first image is edited.
- Afrobeats background music for a fashion ad: rhythm brief and cost
Brief an instrumental Afrobeats bed for a fashion ad: tempo, percussion, bass, and a short loopable bar. $0.125 per take on Sume, mixed on Timeline for $0.10.
- Holiday revisions: $4 caption re-burn vs $24 re-render, 20 clips
For 20 holiday clips, a copy-only revision is 20 caption jobs at $0.20 = $4.00. Re-rendering each at Omni 720p plus captions is $24.00. Three rounds: $12 vs $72
Written by Sume