Sume Images API has no b64_json: turn data[].url into base64

POST /v1/images returns a Sume-hosted data[].url, not b64_json. Fetch the URL and encode it yourself; 202 means poll the job. Python example inside.

4 min readSume
All posts

Sume does not return b64_json from POST /v1/images. Each image comes back as data[].url, a Sume-hosted signed URL, plus media_type. If your code expects base64 inline, download the URL and encode the bytes yourself (example below).

The reason is stated in the Image API docs: Sume already mirrors generated media to storage, so adding base64 to the response would double the bytes. The same page says the API returns data[].url in its place.

What the response contains

The sync response is the OpenRouter-shaped body. Read these fields and ignore the ones you used to read from a base64 client.

Fields of a 200 response from POST /v1/images (read 2026-10-07)
FieldWhat Sume returns
data[].urlSume-hosted, signed URL of the generated image
data[].media_typeFor example image/png or image/webp, following output_format
data[].b64_jsonNot returned
usage.costThe billed USD amount for the call
usage.prompt_tokens, completion_tokens, total_tokensAlways 0 in v1; Sume meters image models per image
created, modelUnix time, and the model id you requested (sume/auto stays sume/auto)

Fetch the URL and encode it

The sample sends one low-quality request, handles the two documented status codes, then encodes the bytes. A 200 carries the image body. A 202 carries the job envelope, because the wait budget (30 seconds on this route) ran out or you asked for async.

Set SUME_API_KEY first. On a 202, poll status_url until terminal is true and then read result_url, as described in Jobs and results.

import base64
import os

import requests

resp = requests.post(
    "https://api.sume.com/v1/images",
    headers={"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"},
    json={"model": "openai/gpt-image-2.5", "prompt": "a red kettle on a white table", "quality": "low"},
    timeout=60,
)
if resp.status_code != 200:
    raise SystemExit(f"not a 200 ({resp.status_code}): poll the job. {resp.text[:200]}")
body = resp.json()
url = body["data"][0]["url"]
b64 = base64.b64encode(requests.get(url, timeout=60).content).decode()
print(body["data"][0]["media_type"], len(b64), "base64 chars, cost", body["usage"]["cost"])

Gotchas when you port a base64 client

  • Branch on the status code, not the body shape: 200 is the image response, 202 is the job envelope. Slow settings (4K, high quality, large n) are the likeliest to fall to 202.
  • Download soon and store the bytes in your own bucket if you need them long term; the URL is signed.
  • Do not read usage.total_tokens for cost. It is 0; usage.cost is the number to log.
  • output_format decides the media type. Send png, jpeg or webp where the catalog row lists it.

A reusable helper

If several services in your stack expect base64, put the download in one function and keep the rest of the code unchanged. Return the bytes and the media type together, so a caller can build a data URI without guessing the format. Set a timeout on the download, check the HTTP status, and fail loudly if the URL has expired.

Two cautions apply. First, base64 inflates the payload by about a third, which is part of why Sume returns a URL: if you forward the string to a browser or a queue, you move more bytes than the file has. Second, do not log the string; log the job id and usage.cost and keep the URL as the reference. When the result is just an input to another Sume call, such as a reference image for a later edit, pass the URL itself.

Where to read the rules

The Image API page lists the response format, the long-running behavior, and the rule that a parameter a model does not list returns 400 unsupported_parameter. For handing the image to another step, such as a video first frame, pass the URL, not base64.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume