Seedream failed generations: free on that route, free on Sume too

OpenRouter's Seedream 5.0 Flash page says failed generations are not charged. Sume's Image API docs say failed or cancelled generations get no charge either.

4 min readSume
All posts

Both routes say a failed image is not billed. OpenRouter's Seedream 5.0 Flash page (read 2026-10-05) states that failed generations are not charged. Sume's Image API docs state that completed generations get the full charge, and that failed or cancelled generations get no charge. The part that differs is what your code sees when a generation fails.

Billing rules compared

Only what each page says is listed.

Failed generation billing, read 2026-10-05
RouteFailed generationSource
OpenRouter, Seedream 5.0 FlashNot chargedOpenRouter model page
Sume Image APINo charge; request returns 502Sume docs, Billing and cancellation
Sume, client disconnectTreated as a failed generation, no chargeSume docs
Sume, completed generationFull endpoint price; pay cost_usd x nSume docs

What your code sees on Sume

A synchronous request waits up to 30 seconds. If the job fails terminally inside that wait, Sume returns 502 with an error envelope carrying code, message, retryable and next_action. If the wait budget runs out, or you send mode: "async", you get 202 with a job envelope instead, and you read the final state from the job's status and result URLs.

Branch on the status code, not the body shape. A 200 is images, a 202 is a job to poll, and a 502 is a terminal failure that you have not been billed for.

  • Check error.retryable before you resubmit. An unreachable reference URL is retryable: false with next_action: fix_input in the docs example.
  • In async and webhook modes the job is the answer, so read its status for the failure state.

A failure-safe request

This handles all three outcomes and prints the cost only when images came back.

import asyncio, os, httpx

async def main():
    body = {"model": "bytedance-seed/seedream-5-lite", "prompt": "a red panda astronaut, studio lighting"}
    headers = {"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"}
    async with httpx.AsyncClient(timeout=60) as c:
        r = await c.post("https://api.sume.com/v1/images", headers=headers, json=body)
    if r.status_code == 200:
        print("cost", r.json()["usage"]["cost"])
    elif r.status_code == 202:
        print("poll", r.json()["data"]["status_url"])
    elif r.status_code == 502:
        e = r.json()["error"]
        print(e["code"], e["retryable"], e["next_action"])
    else:
        print(r.status_code, r.text)

asyncio.run(main())

Limits of this comparison

The OpenRouter page is one sentence, and we did not test what counts as a failure there (a moderation refusal, for example). Sume's docs say nothing different about that case, so for a refused or filtered image, check the job's final status and your usage line before you assume either way. For more on the Sume side, read why an unchanged retry is the wrong move.

Accounting tip

Tag each request with metadata so you can reconcile your own request log against Sume usage later. The Image API docs say Sume stores metadata on the job and does not send it to the provider. Then, at month end, compare the count of 200 responses with the count of billed images: they should match, and the 502s should not appear on the bill.

Sources

Related posts

More in Pricing

All Pricing posts

Written by Sume