Sume image 400 'does not accept aspect_ratio': read supported (Python)

When a Sume image row rejects aspect_ratio with 400 invalid_request, the error carries a supported array. This Python snippet picks the nearest listed ratio.

6 min readSume
All posts

When a Sume image row does not list the aspect ratio you sent, the API returns 400 invalid_request with a message saying the model does not accept aspect_ratio and a supported array of the values it does accept. Read that array and pick the nearest ratio to the one you wanted. The Python below does it in about 25 lines.

The behaviour is in the image generation code on origin/main and matches the Image API docs, which say a value outside a row's descriptor is rejected rather than approximated. I read both on 2026-10-10 and did not call the live API.

The snippet

The helper posts the request and returns the status with the parsed body, including for error responses. When the status is 400 it reads supported, drops auto because that is not a numeric ratio, and picks the listed value whose width over height is closest to the one requested. I look in error first and then at the top level, because I have only read the shape in code and not captured a live body.

import json, os, urllib.error, urllib.request

def post(body):
    req = urllib.request.Request(
        "https://api.sume.com/v1/images",
        data=json.dumps(body).encode(),
        headers={"Authorization": "Bearer " + os.environ["SUME_API_KEY"],
                 "Content-Type": "application/json"},
    )
    try:
        with urllib.request.urlopen(req, timeout=60) as r:
            return r.status, json.load(r)
    except urllib.error.HTTPError as e:
        return e.code, json.load(e)

def nearest(supported, want):
    a, b = want.split(":")
    t = int(a) / int(b)
    ok = [s for s in supported if s != "auto"]
    return min(ok, key=lambda s: abs(int(s.split(":")[0]) / int(s.split(":")[1]) - t))

status, data = post({"model": "x-ai/grok-image", "prompt": "a red kettle", "aspect_ratio": "4:5"})
if status == 400:
    err = data.get("error", data)
    print(nearest(err["supported"], "4:5"))

Which rows will trigger it

Rows that omit common ratios are the likely triggers. The Grok Imagine row does not list 4:5, 5:4 or 21:9. Nano Banana Pro does not list 4:1, 1:4, 8:1 or 1:8. Imagen 4 rows list only five ratios. Higgsfield Soul lists seven. Seedream 5.0 Lite lists nine and no auto.

Rows with fewer ratios than the 13-to-15 norm, catalog read 2026-10-10
RowRatios listedNotable gaps
Imagen 4 Fast and Ultra5everything but the five core ratios
Higgsfield Soul7fewer than most rows
Seedream 5.0 Lite and 4.59no auto
Nano Banana Pro11no 4:1, 1:4, 8:1, 1:8
Grok Imagine13no 4:5, 5:4, 21:9

Do not hide the substitution

Snapping 4:5 to 3:4 changes the crop, and snapping 21:9 to 16:9 changes the width. That may be fine for a draft and wrong for a placement that has hard dimensions. Decide per use: for a draft, log the swap and continue; for a final, fail the job and route it to a row that lists the ratio.

Either way, record the original ratio, the substituted one and the row id next to the result. When someone asks why an ad is slightly narrower than the brief, the log answers it.

  • Retry once with the substituted ratio, not in a loop.
  • Never retry a 400 with the same body.
  • Add a catalog check in CI so the first sight of the error is not in production.
  • Use the list route to see the values before you send.

Related errors

A 400 for an unlisted parameter such as quality on a row that lacks it has the same shape of cause: the descriptor does not list it. The errors and credits page describes the error families, so route them by status code and type rather than by message text, which can be reworded.

Testing the fallback

Unit-test nearest with plain lists, since it has no network dependency. A request for 4:5 against the Grok list should pick 3:4 or 1:1 by the numeric distance, and you should check which one your rule returns and that you like it. 4:5 is 0.8, 3:4 is 0.75 and 1:1 is 1.0, so 3:4 wins. Write that expectation as an assertion so a later change cannot silently alter it.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume