Run an OpenRouter-style image script against Sume: four changes
Moving a script from OpenRouter's /api/v1/images to Sume's /v1/images: base64 becomes a hosted URL, 202 jobs appear, stream and seed return 400. Python check.

To run an OpenRouter-style image script against Sume, change the base URL and key, then handle four differences: results come back as data[].url instead of b64_json, a slow job returns 202 with a job envelope, stream and seed return 400 unsupported_parameter, and the provider routing fields only accept sume. The request field names (model, prompt, n, aspect_ratio, quality, input_references) line up, so most scripts need a small diff, not a rewrite.
The comparison below uses OpenRouter's image generation guide and the Sume Image API page, both read 2026-10-04.
Which endpoints map to which?
| Purpose | OpenRouter | Sume |
|---|---|---|
| Generate | POST /api/v1/images | POST /v1/images |
| List models | GET /api/v1/images/models | GET /v1/images/models |
| Per-endpoint records | GET /api/v1/images/models/{id}/endpoints | GET /v1/images/models/{id}/endpoints |
| Result payload | data[].b64_json plus media_type | data[].url plus media_type |
| Streaming | stream: true on supporting models | 400 streaming_not_supported |
What are the four changes?
- Results are URLs. OpenRouter's page says images return as base64-encoded bytes. Sume returns Sume-hosted HTTPS URLs, so replace your
base64.b64decodestep with a download. - Slow jobs return
202. Sume blocks up to 30 seconds by default, then answers202with a job envelope; read the images fromGET /v1/jobs/{id}/result. Slow configurations such as 4K, high quality or a largenare the likely ones. - Some parameters are rejected, not ignored. Sume validates against each model's published capability descriptors, so
stream,seed,output_compressionand an explicit-pixelsizereturn400 unsupported_parameteror the streaming error. Remove them before the call. - Provider routing is narrow.
provider.onlyandprovider.orderaccept onlysume; any other slug returns400 provider_not_available.ignore,sortandallow_fallbacksare accepted and do nothing.
What does the adapted call look like?
This keeps the OpenRouter field names, drops seed and stream, and downloads from a URL instead of decoding base64.
import os, requests
def generate(prompt, model="bytedance-seed/seedream-4.5"):
r = requests.post(
"https://api.sume.com/v1/images",
headers={"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"},
json={"model": model, "prompt": prompt, "n": 1},
timeout=60,
)
if r.status_code == 202:
raise RuntimeError("slow job: poll " + r.json()["data"]["status_url"])
r.raise_for_status()
body = r.json()
img = requests.get(body["data"][0]["url"], timeout=60)
img.raise_for_status()
return img.content, body["usage"]["cost"]
if __name__ == "__main__":
data, cost = generate("a red panda astronaut, studio lighting")
open("out.png", "wb").write(data)
print("billed USD:", cost)Does the cost field mean the same thing?
On Sume, usage.cost is the billed USD amount, catalog list price times 1.25, and the token counts are always 0. If your OpenRouter code derived cost from tokens, switch it to read cost directly.
Model ids use the same org/slug shape (for example openai/gpt-image-2.5, google/nano-banana-2, bytedance-seed/seedream-4.5), and legacy bare ids still resolve as aliases. Check GET /v1/images/models for what your key can call before you hard-code a list.
Sources
Related posts
More in Developers
- Run your own 10-clip word error test on Sume STT for 10 cents
Microsoft ranks MAI-Transcribe-2-Streaming first on Artificial Analysis. To know your own audio, score 10 one-minute clips with a 15-line WER function.
- Same prompt, four Sume video models: a Python script that logs cost
Submit one prompt to wan-3.0, minimax-h3, minimax-h3-max and seedance-2.5 on /v1/videos, poll each job, and print usage.cost per clip. Runnable as written.
- What to save from a Sume run when batch results expire at 30 days
OpenAI keeps batch output 30 days, Anthropic 29, Gemini 6 weeks. Which Sume run receipt fields to store so your records outlive any vendor retention window.
- Why a Sume scheduled run's events_url is null, and what to poll
Action runs always return events_url null; Format runs expose a phase timeline. What to poll for a scheduled run and how to read skipped.
Written by Sume