The useful Seedream 5.5 migration is a file workflow, not just a model-name substitution: generate an image, inspect the returned task, save the image, then send those saved bytes as the reference for an edit.
Seedream 5.5 on AIHubMix currently uses doubao-seedream-5-5 and POST /ai/v1/images/generations. This tutorial creates a ceramic-mug product visual, then requests a color-only edit.
Scope: documentation checked on November 12, 2026. Code received local syntax and offline helper checks only; no generation request was executed. They are not evidence of output quality, editing fidelity, latency, or an actual bill.
1. Prepare one small, explicit request
Use Python 3.10+ with requests. In Bash, install the dependency and enter the key without putting its value into shell history:
python3 -m pip install requests
read -r -s -p 'AIHubMix API key: ' AIHUBMIX_API_KEY
printf '\n'
export AIHUBMIX_API_KEY
For deployed code, inject the environment variable through your secret manager. Keep the key out of source files, request examples, logs, notebooks, and browser-side code. Before either POST, enable the account's task feature as required by the async task guide. That prerequisite applies to native /ai/v1 media task endpoints, including synchronous waiting; it is separate from choosing background execution.
Read the current model endpoint schema, selecting the entry by path, not array position. Its model-specific constraints take precedence over a shared parameter list.
| Setting | Value used here |
|---|---|
| Model | doubao-seedream-5-5 |
| Route | POST /ai/v1/images/generations |
| Body | Flat JSON |
| Size | 2048x2048 |
| Image count | n = 1 |
| File format | png |
| Response format | url |
The schema requires model and a nonempty prompt, restricts n to 1, and lists png and jpeg output. It accepts an image URL, Base64 input, or {url} object; images accepts an array with a published maximum of 14 references. That limit has not been runtime-tested here. This example uses one reference and the default size explicitly, with no outer input object or extra extensions.
2. Generate, inspect the task, and save the PNG
The unified image API documentation defines a task response with id, object: image, model, status, output, and error. Do not read it as legacy data[0].url.
Read the short workflow first; the full setup and download helper is in the folded Appendix below. Before executing these examples, load that appendix block once into the same Python session. It defines settings, api_json, and collect without submitting an image request. Then run the short blocks in order. Each POST is a separate paid action; do not rerun it to repair a missing response or failed download.
Submit the image request once
generation = {
**settings,
"prompt": "Product photograph of one matte blue ceramic mug on a cream tabletop, "
"soft side lighting, plain warm-gray background, no text or logos.",
}
task = api_json("POST", IMAGES + "/generations", generation)
Inspect the task, then save the PNG
Omitting async means synchronous waiting. HTTP 200 can still contain status: failed. The helper saves the task ID before inspecting status: only completed proceeds to downloading; failed, cancelled, and unknown states stop it. pending and in_progress need existing-task inspection, not another generation.
collect(task, "mug-blue")
This single call uses the creating key to retrieve the completed output, checks PNG bytes, and saves mug-blue.png. It makes no automatic creation retry and refuses unexpected download hosts or redirects before forwarding the key. The full implementation is in the appendix; this is documentation-based behavior, not a verified generation result.
The model guide says output URLs last roughly 30 minutes. Download promptly with the same Bearer header and store the bytes yourself. expires_at: null is not a permanent-retention promise. If b64_json is populated, decode it instead of downloading a URL.
3. Edit the local file, not its protected download URL
Inspect the saved image yourself before spending on an edit. A content_url that your authenticated client can download is not necessarily a public URL the image provider can fetch. Re-upload the local PNG as a Data URI:
reference = Path("mug-blue.png").read_bytes()
if not reference.startswith(b"\x89PNG\r\n\x1a\n"):
raise SystemExit("The editing reference must be the saved PNG.")
edit = {
**settings,
"prompt": "Change only the mug color from blue to forest green. Preserve the mug shape, "
"camera angle, tabletop, background, lighting, and shadows.",
"image": "data:image/png;base64," + base64.b64encode(reference).decode("ascii"),
}
task = api_json("POST", IMAGES + "/generations", edit)
collect(task, "mug-green")
This requests preservation; it does not demonstrate that preservation occurred. Review the result before another paid round. The entire JSON request, including inline encoding, must fit within 32 MiB; model-specific media limits also apply. A genuinely public, provider-reachable image URL is another accepted input option.
4. Optional: async waiting and interruption recovery
The account prerequisite in section 1 is separate from the execution mode. 403 async_not_enabled means the task feature needs activation in the console. Only background execution is optional.
If you prefer background generation, build the same generation dictionary, then use the two lines below instead of both the synchronous task = api_json(...) line and its separate collect(...) call. Choose the mode before submitting; do not run both versions:
task = api_json("POST", IMAGES + "/generations", {**generation, "async": True})
collect(task, "mug-blue", poll=True)
The helper queries GET /ai/v1/images/{id} approximately every 15 seconds until a terminal state. That is the documented polling recommendation, not a server-enforced interval. Webhooks require async: true; a receiver is outside this tutorial.
If collection stopped, reload only the shared setup/helper block from the appendix in a fresh session if needed, not a creation block. If an ID was saved, resume without any POST:
task_id = Path("mug-blue.task-id").read_text(encoding="utf-8").strip()
task = api_json("GET", IMAGES + "/" + quote(task_id, safe=""))
collect(task, "mug-blue", poll=True)
Use the corresponding mug-green stem to recover the edit. If the creation response disappeared before an ID was saved, inspect recent tasks instead:
recent = api_json("GET", IMAGES + "?limit=20&order=desc")
for row in recent.get("data", []):
print(row.get("id"), row.get("model"), row.get("status"), row.get("created_at"))
Match the model and creation time; do not blindly select the newest task. Use next_after as the next request's after cursor if needed. If the match is ambiguous, contact support rather than submit again. A list is a snapshot; use task details for polling. A row's output_error means output could not be read, not that generation produced nothing or charges changed.
Keep using the creating key. In particular, unified task list, detail, and content access is token-isolated: another key on the same account cannot read those tasks. A local interruption does not prove failure or absence of charges.
5. Migration mistakes and current errors
The legacy Doubao adapter documentation lists older IDs under /v1/models/doubao/<id>/predictions with an input envelope. Its inventory does not list 5.5. Use the discoverable 5.5 route and flat body here; no legacy-generation failure was tested. Copy the current hyphenated ID exactly rather than assuming a dotted spelling is an alias.
Do not carry over top-level quality, background, aspect_ratio, or seed: they are absent from this model's current schema. ByteDance's launch announcement describes native transparent RGBA output, but the gateway schema exposes neither background nor layer_decomposition. Selecting PNG alone is not a verified transparency recipe.
These are documented unified image errors, not observed 5.5 responses. Check HTTP status separately from task status, and branch on code, not a full-message string.
| Where | Code | Next action |
|---|---|---|
| HTTP 400 | schema_violation | Correct schema fields |
| HTTP 400 | capability_not_supported | Remove unsupported options |
| HTTP 504 | sync_timeout | Recover; contact support |
| HTTP 502 | task_status_unavailable | Do not resubmit |
| HTTP 422 | output_blocked | Revise content |
| HTTP 403 | async_not_enabled | Activate task feature |
| Failed task | result_delivery_failed | Investigate existing result |
For HTTP 502 task_status_unavailable, the guide states:
The model provider task state could not be confirmed. Do not submit it again. Contact support with the task ID or request ID.
For task-level result_delivery_failed, it states:
The generated result could not be delivered. Do not resubmit automatically. Contact support with the task ID.
A sync_timeout does not establish that execution never happened. Retain error.tid, use a task ID only if one was actually returned, and ask support to resolve uncertain outcomes. For a recoverable download failure, retry retrieval rather than generation. 410 artifact_expired is different: that artifact can no longer be retrieved. Check saved files first; if a replacement is still needed, deliberately authorize a new paid generation. For 429 too_many_downloads, check saved copies or contact support; waiting alone does not guarantee renewed access.
The guide explicitly waives the generation fee for output_blocked, including when reported in a failed task. Do not extend that exception to every failed or moderated request; output_policy_violation follows the existing moderation billing rules.
6. Budget for the workflow, not just one output
Sina Technology's launch report reports approximately CNY 0.28 for one 2K square edit, combining image input and output, with text tokens free. That is press reporting, not a verified official rate sheet or an AIHubMix quote.
An illustration: two paid edit rounds of an existing image, each matching that rounded scenario, are approximately 2 × CNY 0.28 = CNY 0.56. This excludes initial text-to-image generation, extra reference images, additional candidates, and retries. It is not a measured bill.
For this generate-then-edit example, use total = initial generation charge + reference-edit charge. A full official 5.5 tariff was not established from the examined pages, so this tutorial gives neither a fixed total nor a precise old/new savings ratio.
Before submitting:
- Confirm the current ID, route, flat body, and model schema.
- Keep the key in the environment; keep task IDs for recovery.
- Treat each POST as a separate paid action, never a download retry.
- Inspect task status and the saved image before editing.
- Re-upload local bytes with the correct MIME type.
- Recover uncertain tasks before considering another generation.
Start with one image and one deliberate edit; add more rounds only after reviewing the files.
Appendix: shared setup and download helper
Skip this appendix when reading the workflow. Before executing the short examples, expand and load this block once into the same Python session. It configures the session and defines functions without generating an image. To recover an interrupted task in a fresh session, reload only this block—not a creation or editing block.
Expand the full helper — load once before the short examples; no image request
import base64
import os
import time
from pathlib import Path
from urllib.parse import quote, urlsplit
import requests
BASE = "https://aihubmix.com"
IMAGES = "/ai/v1/images"
settings = {
"model": "doubao-seedream-5-5",
"size": "2048x2048",
"n": 1,
"output_format": "png",
"response_format": "url",
}
client = requests.Session()
client.headers["Authorization"] = "Bearer " + os.environ["AIHUBMIX_API_KEY"]
def api_json(method, path, body=None):
try:
response = client.request(
method, BASE + path, json=body,
timeout=(15, 600), allow_redirects=False,
)
except requests.RequestException:
raise SystemExit(
"Connection interrupted. Recover the existing task before another POST."
) from None
try:
data = response.json()
except ValueError:
raise SystemExit(
f"HTTP {response.status_code}: non-JSON response; recover before another POST."
) from None
if not isinstance(data, dict):
raise SystemExit("Unexpected response; recover before another POST.")
if not 200 <= response.status_code < 300:
error = data.get("error") or {}
raise SystemExit(
f"HTTP {response.status_code}: code={error.get('code')}, "
f"tid={error.get('tid')}. No automatic generation retry."
)
return data
def collect(task, stem, poll=False):
task_id = task.get("id")
if not isinstance(task_id, str) or not task_id:
raise SystemExit("No task ID returned. Recover from the task list; do not resubmit.")
Path(stem + ".task-id").write_text(task_id + "\n", encoding="utf-8")
while True:
status = task.get("status")
print("Task:", task_id, "status:", status)
if status == "completed":
break
if status not in {"pending", "in_progress"}:
error = task.get("error") or {}
raise SystemExit(
f"Stopped: status={status}, code={error.get('code')}; task={task_id}."
)
if not poll:
raise SystemExit("Task still active. Query the saved ID; do not recreate it.")
time.sleep(15)
task = api_json("GET", IMAGES + "/" + quote(task_id, safe=""))
output = task.get("output") or []
if not output:
raise SystemExit("Completed without readable output; investigate the saved task.")
item = output[0]
if item.get("b64_json"):
image_bytes = base64.b64decode(item["b64_json"], validate=True)
else:
url = item.get("content_url")
if not isinstance(url, str):
raise SystemExit("Missing content_url; investigate the saved task.")
parsed = urlsplit(url)
if (parsed.scheme, parsed.netloc) != ("https", "aihubmix.com"):
raise SystemExit("Unexpected download origin; key was not forwarded.")
try:
response = client.get(url, timeout=(15, 120), allow_redirects=False)
except requests.RequestException:
raise SystemExit("Download interrupted; recover this task, not a new generation.") from None
if not 200 <= response.status_code < 300:
try:
detail = response.json()
except ValueError:
detail = {}
error = detail.get("error", {}) if isinstance(detail, dict) else {}
code = error.get("code") if isinstance(error, dict) else None
if response.status_code == 410 or code == "artifact_expired":
raise SystemExit("Artifact expired; retrieval cannot recover it. Check saved files first.")
if code == "too_many_downloads":
raise SystemExit("Download allowance exhausted; check saved files or contact support.")
raise SystemExit(
f"Download HTTP {response.status_code}, code={code}; "
"inspect the saved task. Retry retrieval only if recoverable."
)
image_bytes = response.content
if not image_bytes.startswith(b"\x89PNG\r\n\x1a\n"):
raise SystemExit("Expected PNG bytes; stopped before saving or reusing the result.")
destination = Path(stem + ".png")
destination.write_bytes(image_bytes)
print("Saved:", destination)
return destination
The 600-second read timeout is a client choice, not a service latency promise. The PNG check matches this tutorial's explicit output format; adapt it if you choose JPEG.
FAQ
Which model ID and endpoint should Seedream 5.5 use?
Use doubao-seedream-5-5 with POST /ai/v1/images/generations and flat JSON. Do not assume another spelling or a legacy adapter is equivalent.
Does this tutorial include tested generation results?
No. The code is documentation-based, with local syntax and offline helper checks only. No inference request, image-quality measurement, latency measurement, or billing test was executed.
Is async required?
No; synchronous waiting is the default. Background execution uses Boolean async: true. Account-level task-feature activation is a prerequisite for the native media task endpoints even when waiting synchronously.
Does HTTP 200 mean the image succeeded?
No. Inspect task status and its error. A task can be failed even when the HTTP request succeeds; download only after completed.
Can a result download URL be the next editing reference?
Do not assume so. Download it with the creating key, then encode the local image as a Data URI, or provide a genuinely public image URL.
What should happen after a timeout or lost response?
Do not automatically repeat generation. Inspect the saved task ID or recover it from the image list with the creating key. Keep request IDs and contact support when the outcome is uncertain.
Does choosing PNG guarantee transparent output or a CNY 0.28 bill?
Neither. The schema provides no verified transparency control. CNY 0.28 is a press-reported approximate 2K editing example, not an AIHubMix quote or a fixed price for text-to-image plus editing.
Keep reading: the Seedream 5.5 series
- Before changing an existing 5.0 Pro workflow, weigh the upgrade: Seedream 5.5 vs 5.0 Pro: is it worth upgrading?
- If the same workflow could use a different image model, compare the trade-offs: Seedream 5.5 vs GPT Image 2.6 Sunburst: which should you choose?
Sources
- Seedream 5.5 launch announcement — ByteDance Seed; vendor capability claims.
- Current Seedream 5.5 endpoint schema — AIHubMix; request fields and model-specific constraints.
- Unified image generation API — AIHubMix; tasks, downloads, and current errors.
- Seedream 5.5 model guide — AIHubMix; model ID and short-lived authenticated downloads; use the unified API document for route-specific errors.
- Seedream 5.5 launch coverage — Sina Technology; approximate editing-cost scenario, not an official tariff.



