What “Seedream 5.0 Pro API Batch” Actually Means
Three phrases are often treated as synonyms. They are not.
| Term |
Meaning |
Current behavior |
| Multi-reference input |
Several source images guide one edit |
The GPT Proto image-edit endpoint accepts up to 10 references |
| Multiple outputs |
One prompt returns several separate images |
Not exposed for Seedream 5.0 Pro |
| Bulk generation |
Many SKU-specific requests run through a queue |
The workflow built below |
Multi-reference input improves one result. It does not create ten results. A product front view, a material close-up, and a lighting reference can all guide one final image. To create images for 100 products, the client still sends 100 requests.
That distinction affects everything downstream. One SKU per request gives each output an owner, filename, cost record, and retry history. Concurrency makes the queue move faster; it does not turn the model into a multi-output endpoint. The underlying model behavior is documented in BytePlus’s image-generation matrix, while GPT Proto exposes the model through its own /api/v3/ routes.
What You Will Build
The data moves through five stages:
CSV catalog → prompt and references → controlled request pool → downloaded files → human review
The script uses synchronous requests with enable_sync_mode: true. That choice is deliberate. GPT Proto’s live model page documents an asynchronous result_id workflow, but the complete status and output response schema is not published on that page. A production tutorial should not invent those fields.
Synchronous mode still supports bulk generation. Each worker handles one product from request through download, while ThreadPoolExecutor lets a small number of workers run at the same time. The manifest is written by the main thread after each worker finishes, so interrupted runs can skip files that are already present.
For a later high-throughput version, set enable_sync_mode to false only after you have captured a live response and confirmed the current task ID, status, output, and error fields. Then store the returned result_id before polling /api/v3/predictions/{result_id}/result.
What You Need Before Starting
Install Python and the single dependency:
python --version
python -m pip install requests
Create a GPT Proto API key, then keep it in an environment variable:
export GPTPROTO_API_KEY="your-key-here"
The current live API examples use this header format:
Authorization: Bearer YOUR_API_KEY
You also need product references at public HTTPS URLs. A path such as /Users/me/photos/mug.jpg exists only on your computer. The remote API cannot fetch it. Put the files on a CDN, object-storage bucket, or another HTTPS location that the API can reach without a login cookie.
Start with a small catalog slice. Pick products with different shapes, materials, and label complexity. A simple mug can pass while a bag with multiple straps fails, so one easy test is not enough to approve a shared prompt.
Estimate the Batch Cost Before You Run It
GPT Proto’s live Seedream 5.0 Pro pricing was checked on September 17, 2026. Use the model page for per-image rates and the GPT Proto pricing page for account-level pricing information.
| Output |
Base price, including the first reference |
Each additional reference |
| 1K |
$0.0405 per image |
+$0.0027 |
| 2K |
$0.0810 per image |
+$0.0027 |
The current Seedream 5.0 Pro Image Edit API accepts up to 10 reference images. The first is included in the base price. References two through ten add $0.0027 each.
That produces these planning figures before retries or rejected generations:
| Batch |
Estimated API cost |
| 100 one-reference images at 1K |
$4.05 |
| 100 one-reference images at 2K |
$8.10 |
| 1,000 one-reference images at 1K |
$40.50 |
| 1,000 one-reference images at 2K |
$81.00 |
| 100 images with three references at 1K |
$4.59 |
| 100 images with three references at 2K |
$8.64 |
The raw-generation bill is easy to estimate. The approved-image cost is not. If a product needs several attempts, every generated image counts. Use this formula for a more realistic budget:
estimated catalog cost =
number of SKUs
× average attempts per approved image
× (base output price + paid reference-image cost)
Use 1K to test the prompt system across representative products. Do not treat it as a guaranteed preview of a later 2K call. The 2K request creates a new image, so composition and details can change. Review the final-resolution result again.
Step 1: Create a Product CSV
Save this as products.csv:
sku,name,color,material,angle,background,product_image_url,detail_image_url,style_reference_url,avoid
MUG-001,Stackable coffee mug,matte navy,stoneware,three-quarter,soft warm gray,https://cdn.example.com/mug-front.jpg,https://cdn.example.com/mug-glaze.jpg,,steam or hands
BAG-014,Compact crossbody bag,forest green,pebbled leather,front three-quarter,light beige,https://cdn.example.com/bag-front.jpg,https://cdn.example.com/bag-texture.jpg,https://cdn.example.com/style-neutral.jpg,model or extra straps
Replace every cdn.example.com URL before running the script. The CSV fields serve different jobs:
sku is the permanent product key. It also becomes part of the output filename.
name, color, material, angle, and background hold facts that change by product.
product_image_url is the identity reference and is required.
detail_image_url can show material, controls, hardware, or a label close-up.
style_reference_url controls lighting and background treatment, not product identity.
avoid lists a few visible mistakes that matter for that SKU.
Keep values short and factual. Do not put an entire creative brief into one cell. Do not put the API key in the CSV, source code, or manifest.
The script below rejects duplicate SKUs, missing product URLs, non-HTTPS references, and more than ten references before sending a paid request.
Step 2: Give Each Reference Image One Job
A Seedream 5.0 Pro API multi-reference image request works better when the prompt explains why each image exists. Use a stable order:
Product identity reference: silhouette, proportions, visible design, and packaging.
Detail reference: material, hardware, controls, or label treatment.
Style reference: lighting, surface, palette, and background only.
The generated prompt will include instructions such as:
Image 1 is the product identity reference. Preserve its silhouette, proportions, visible design, and packaging. Image 2 is the detail reference. Use it for material and hardware details. Image 3 is the lighting and background reference only. Do not copy objects from it.
Start with one to three useful references. Ten unlabeled inputs cost more and can conflict with one another. More images are not automatically more accurate.
A ComfyUI launch post describes stable product features across scene variations as a production use case. Treat that as a workflow claim, not an independent benchmark. Product identity still needs a side-by-side review against the source.
Step 3: Build One Reusable Product Prompt
Keep catalog-wide rules in code and SKU facts in the CSV. A useful order is:
asset type
→ product identity and protected details
→ requested background
→ camera and composition
→ lighting and shadow
→ material behavior
→ exclusions
For the mug row, the script produces instructions close to this:
Create one square e-commerce hero photograph of the Stackable coffee mug.
The product is matte navy stoneware. Preserve the product identity, silhouette,
proportions, visible design, color family, material, and packaging details from
the references. Show a three-quarter view on a soft warm gray background.
Use a large diffused key light from the upper left and a natural contact shadow.
Keep the whole product inside the frame with crop-safe space. Return one product
image, not a collage or contact sheet. Do not add steam or hands. Do not invent
logos, labels, accessories, duplicate products, handles, straps, or controls.
Name observable details. “Premium quality” gives the model little physical information. “Soft contact shadow falling back-right” does. Keep exclusions short because the current GPT Proto request does not expose a separate negative_prompt field.
For more prompt patterns, use the Seedream 5.0 Pro Prompt Guide.
Step 4: Test One Product Before Starting the Queue
Run one synchronous image-edit request first:
curl --request POST \
--url "https://gptproto.com/api/v3/doubao/dola-seedream-5-0-pro-260628/image-edit" \
--header "Authorization: Bearer $GPTPROTO_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"images": [
"https://your-cdn.example.com/mug-front.jpg",
"https://your-cdn.example.com/mug-glaze.jpg"
],
"prompt": "Create one square e-commerce hero photograph of the referenced mug. Image 1 is the product identity reference. Preserve its silhouette, proportions, handle, color, and visible design. Image 2 is the glaze detail reference. Show a three-quarter view on a soft warm gray background with a natural contact shadow. Keep the entire product inside the frame. No hands, steam, extra text, duplicate products, or collage.",
"size": "1024x1024",
"enable_base64_output": false,
"enable_sync_mode": true,
"output_format": "png"
}'
The endpoint and current Bearer header match the live GPT Proto model example. Print and retain the first successful JSON response before adding more automation. Check that it contains an output your client can download.
Do not continue because the image merely looks attractive. Compare it with the references:
Is the silhouette recognizable?
Are the color and material acceptably close?
Did the model add or remove a handle, strap, button, cap, or label?
Is required text correct character by character?
Does the product make physical contact with the surface?
Is there enough crop-safe space for the intended marketplace placement?
Test one representative product and one difficult product. Both should pass before the full queue starts.
Step 5: Run One Request per SKU
The batch script sends the same image-edit payload used in the cURL test. The variables come from each CSV row. MAX_WORKERS controls how many independent requests can be active at once.
Start with a small value such as 2 or 3. The BytePlus documentation may publish limits for direct BytePlus accounts, but those numbers are not automatically your GPT Proto account limits. If the API returns 429, reduce the worker count and follow the response’s retry guidance.
The script uses deterministic names:
{sku}_hero_{attempt:02d}.{extension}
Examples include MUG-001_hero_01.png and BAG-014_hero_02.png. The attempt remains visible, so a reviewer can trace the selected file back to the manifest.
Step 6: Add Retries and Resume Support
Not every failure should trigger another paid request.
The script retries explicit 429 and 5xx responses with exponential backoff and jitter. It also honors a numeric Retry-After header when present. Authentication, invalid-parameter, balance, and moderation errors are recorded without an unchanged resubmission.
Read timeouts and dropped connections are different. The server may have accepted the request even though the client did not receive the response. Without a documented idempotency key, an automatic resubmission could create a second image and a second charge. The script marks those cases manual_check instead. That is slower than blind retrying. It is also safer for a production budget.
After each worker finishes, the main thread updates manifest.json through an atomic file replacement. On restart:
an item with downloaded, approved, or manual_review status and an existing local file is skipped;
a failed_retryable item continues only if attempts remain;
a manual_check item is not submitted again automatically; and
a failed item retains its error and is skipped until an operator fixes the cause, then removes that SKU entry from the manifest or changes its status to failed_retryable.
This synchronous design cannot save an asynchronous task ID. If you later adopt the documented background workflow, add task_id to the same manifest and save the live result_id immediately after submission.
Complete Python Script for Bulk Seedream Product Images
Save the following as bulk_seedream_products.py beside products.csv.
from __future__ import annotations
import base64
import csv
import json
import os
import random
import re
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from urllib.parse import urlparse
import requests
API_URL = (
"https://gptproto.com/api/v3/doubao/"
"dola-seedream-5-0-pro-260628/image-edit"
)
CSV_PATH = Path(os.getenv("PRODUCTS_CSV", "products.csv"))
OUTPUT_DIR = Path(os.getenv("OUTPUT_DIR", "generated"))
MANIFEST_PATH = Path(os.getenv("MANIFEST_PATH", "manifest.json"))
SIZE = os.getenv("SEEDREAM_SIZE", "1024x1024")
OUTPUT_FORMAT = os.getenv("SEEDREAM_OUTPUT_FORMAT", "png")
MAX_WORKERS = int(os.getenv("MAX_WORKERS", "3"))
MAX_ATTEMPTS = int(os.getenv("MAX_ATTEMPTS", "4"))
REQUEST_TIMEOUT = int(os.getenv("REQUEST_TIMEOUT", "300"))
PROMPT_VERSION = os.getenv("PROMPT_VERSION", "product-hero-v1")
REFERENCE_FIELDS = (
(
"product_image_url",
"the product identity reference. Preserve its silhouette, proportions, "
"visible design, color family, material, and packaging",
),
(
"detail_image_url",
"the detail reference. Use it for material, hardware, controls, and label details",
),
(
"style_reference_url",
"the lighting and background reference only. Do not copy products or props from it",
),
)
def now_utc() -> str:
return datetime.now(timezone.utc).isoformat()
def is_https_url(value: str) -> bool:
parsed = urlparse(value)
return parsed.scheme == "https" and bool(parsed.netloc)
def clean_filename(value: str) -> str:
cleaned = re.sub(r"[^A-Za-z0-9._-]+", "-", value.strip())
return cleaned.strip("-._") or "product"
def reference_data(row: dict[str, str]) -> tuple[list[str], list[str]]:
urls: list[str] = []
role_lines: list[str] = []
for field, role in REFERENCE_FIELDS:
url = row.get(field, "").strip()
if not url:
continue
urls.append(url)
role_lines.append(f"Image {len(urls)} is {role}.")
return urls, role_lines
def validate_and_load_rows(path: Path) -> list[dict[str, str]]:
required = {
"sku",
"name",
"color",
"material",
"angle",
"background",
"product_image_url",
"detail_image_url",
"style_reference_url",
"avoid",
}
with path.open("r", encoding="utf-8-sig", newline="") as handle:
reader = csv.DictReader(handle)
fields = set(reader.fieldnames or [])
missing = required - fields
if missing:
raise ValueError(f"CSV is missing columns: {sorted(missing)}")
rows: list[dict[str, str]] = []
seen_skus: set[str] = set()
for line_number, raw_row in enumerate(reader, start=2):
row = {key: (value or "").strip() for key, value in raw_row.items()}
sku = row["sku"]
if not sku:
raise ValueError(f"Line {line_number}: sku is required")
if sku in seen_skus:
raise ValueError(f"Line {line_number}: duplicate sku {sku!r}")
seen_skus.add(sku)
urls, _ = reference_data(row)
if not row["product_image_url"]:
raise ValueError(
f"Line {line_number}: product_image_url is required"
)
if len(urls) > 10:
raise ValueError(
f"Line {line_number}: {sku} has more than 10 references"
)
invalid_urls = [url for url in urls if not is_https_url(url)]
if invalid_urls:
raise ValueError(
f"Line {line_number}: references must be public HTTPS URLs: "
f"{invalid_urls}"
)
rows.append(row)
if not rows:
raise ValueError("The CSV contains no product rows")
return rows
def build_prompt(row: dict[str, str], role_lines: list[str]) -> str:
avoid = row.get("avoid") or "extra products or unrelated props"
references = " ".join(role_lines)
return (
f"Create one square e-commerce hero photograph of the {row['name']}. "
f"{references} "
f"The product is {row['color']} {row['material']}. "
"Preserve the product identity, silhouette, proportions, visible design, "
"color family, material, and packaging details from the references. "
f"Show a {row['angle']} view on a {row['background']} background. "
"Use a large diffused key light from the upper left and a natural contact "
"shadow falling back-right. Keep the whole product inside the frame with "
"crop-safe space. Return one product image, not a collage or contact sheet. "
f"Do not add {avoid}. Do not invent logos, labels, accessories, duplicate "
"products, handles, straps, buttons, caps, or controls."
)
def load_manifest(path: Path) -> dict[str, Any]:
if not path.exists():
return {"prompt_version": PROMPT_VERSION, "items": {}}
with path.open("r", encoding="utf-8") as handle:
data = json.load(handle)
if not isinstance(data.get("items"), dict):
raise ValueError("manifest.json must contain an object named 'items'")
return data
def save_manifest(path: Path, manifest: dict[str, Any]) -> None:
temporary = path.with_suffix(path.suffix + ".tmp")
with temporary.open("w", encoding="utf-8") as handle:
json.dump(manifest, handle, indent=2, ensure_ascii=False)
handle.write("\n")
os.replace(temporary, path)
def output_items(payload: dict[str, Any]) -> list[Any]:
data = payload.get("data")
containers = [data, payload] if isinstance(data, dict) else [payload]
for container in containers:
for key in ("outputs", "output", "images", "image"):
value = container.get(key)
if value:
return value if isinstance(value, list) else [value]
raise ValueError(
"No output field found. Inspect the saved response and update output_items() "
"to match the current API response."
)
def output_value(item: Any) -> str:
if isinstance(item, str):
return item
if isinstance(item, dict):
for key in ("url", "image_url", "b64_json", "base64"):
value = item.get(key)
if isinstance(value, str) and value:
return value
raise ValueError(f"Unsupported output item: {item!r}")
def extension_from_content_type(content_type: str) -> str:
mime = content_type.split(";", 1)[0].strip().lower()
return {
"image/png": ".png",
"image/jpeg": ".jpg",
"image/webp": ".webp",
}.get(mime, f".{OUTPUT_FORMAT}")
def save_output(value: str, file_stem: Path) -> tuple[Path, str | None]:
if value.startswith("data:image/"):
header, encoded = value.split(",", 1)
mime = header.split(";", 1)[0].removeprefix("data:")
destination = file_stem.with_suffix(extension_from_content_type(mime))
destination.write_bytes(base64.b64decode(encoded))
return destination, None
if value.startswith("https://") or value.startswith("http://"):
response = requests.get(value, timeout=REQUEST_TIMEOUT)
response.raise_for_status()
extension = extension_from_content_type(
response.headers.get("Content-Type", "")
)
destination = file_stem.with_suffix(extension)
destination.write_bytes(response.content)
return destination, value
# Some responses return raw Base64 without a data URI.
try:
decoded = base64.b64decode(value, validate=True)
except ValueError as exc:
raise ValueError("Output is neither a URL nor valid Base64") from exc
destination = file_stem.with_suffix(f".{OUTPUT_FORMAT}")
destination.write_bytes(decoded)
return destination, None
def backoff_seconds(attempt: int, response: requests.Response | None) -> float:
if response is not None:
retry_after = response.headers.get("Retry-After")
if retry_after:
try:
return max(0.0, float(retry_after))
except ValueError:
pass
return min(60.0, (2 ** (attempt - 1)) + random.uniform(0.0, 1.0))
def result_record(
row: dict[str, str],
references: list[str],
status: str,
attempt: int,
*,
output_url: str | None = None,
local_path: str | None = None,
error: str | None = None,
created_at: str | None = None,
) -> dict[str, Any]:
timestamp = now_utc()
return {
"sku": row["sku"],
"asset_type": "hero",
"status": status,
"attempt": attempt,
"prompt_version": PROMPT_VERSION,
"reference_urls": references,
"output_url": output_url,
"local_path": local_path,
"error": error,
"requester": "",
"reviewer": "",
"approval_status": "pending" if status == "downloaded" else "",
"created_at": created_at or timestamp,
"updated_at": timestamp,
}
def process_product(
row: dict[str, str], previous: dict[str, Any] | None
) -> dict[str, Any]:
api_key = os.environ["GPTPROTO_API_KEY"]
references, role_lines = reference_data(row)
prompt = build_prompt(row, role_lines)
created_at = (previous or {}).get("created_at") or now_utc()
first_attempt = int((previous or {}).get("attempt", 0)) + 1
if first_attempt > MAX_ATTEMPTS:
return result_record(
row,
references,
"failed",
first_attempt - 1,
error="Maximum attempts already reached",
created_at=created_at,
)
headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
}
body = {
"images": references,
"prompt": prompt,
"size": SIZE,
"enable_base64_output": False,
"enable_sync_mode": True,
"output_format": OUTPUT_FORMAT,
}
for attempt in range(first_attempt, MAX_ATTEMPTS + 1):
response: requests.Response | None = None
try:
response = requests.post(
API_URL,
headers=headers,
json=body,
timeout=REQUEST_TIMEOUT,
)
if response.status_code == 429 or response.status_code >= 500:
message = f"HTTP {response.status_code}: {response.text[:500]}"
if attempt == MAX_ATTEMPTS:
return result_record(
row,
references,
"failed_retryable",
attempt,
error=message,
created_at=created_at,
)
time.sleep(backoff_seconds(attempt, response))
continue
response.raise_for_status()
payload = response.json()
item = output_items(payload)[0]
value = output_value(item)
file_stem = OUTPUT_DIR / (
f"{clean_filename(row['sku'])}_hero_{attempt:02d}"
)
local_file, output_url = save_output(value, file_stem)
return result_record(
row,
references,
"downloaded",
attempt,
output_url=output_url,
local_path=str(local_file),
created_at=created_at,
)
except requests.exceptions.ConnectTimeout as exc:
# No connection was established; a bounded retry is reasonable.
if attempt == MAX_ATTEMPTS:
return result_record(
row,
references,
"failed_retryable",
attempt,
error=str(exc),
created_at=created_at,
)
time.sleep(backoff_seconds(attempt, response))
except (
requests.exceptions.ReadTimeout,
requests.exceptions.ConnectionError,
) as exc:
# The server may have accepted the request. Avoid a blind resubmission.
return result_record(
row,
references,
"manual_check",
attempt,
error=f"Ambiguous network failure; verify account history: {exc}",
created_at=created_at,
)
except requests.exceptions.HTTPError as exc:
status_code = response.status_code if response is not None else "unknown"
details = response.text[:500] if response is not None else str(exc)
return result_record(
row,
references,
"failed",
attempt,
error=f"HTTP {status_code}: {details}",
created_at=created_at,
)
except (ValueError, KeyError, json.JSONDecodeError) as exc:
return result_record(
row,
references,
"manual_check",
attempt,
error=str(exc),
created_at=created_at,
)
return result_record(
row,
references,
"failed",
MAX_ATTEMPTS,
error="Request ended without a result",
created_at=created_at,
)
def should_skip(entry: dict[str, Any] | None) -> bool:
if not entry:
return False
if entry.get("status") in {"manual_check", "failed"}:
return True
if entry.get("status") not in {"downloaded", "manual_review", "approved"}:
return False
local_path = entry.get("local_path")
return bool(local_path and Path(local_path).exists())
def main() -> None:
if not os.getenv("GPTPROTO_API_KEY"):
raise SystemExit("Set GPTPROTO_API_KEY before running the script")
if MAX_WORKERS < 1:
raise SystemExit("MAX_WORKERS must be at least 1")
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
rows = validate_and_load_rows(CSV_PATH)
manifest = load_manifest(MANIFEST_PATH)
items: dict[str, Any] = manifest["items"]
pending: list[dict[str, str]] = []
for row in rows:
entry = items.get(row["sku"])
if should_skip(entry):
print(f"SKIP {row['sku']}: {entry['status']}")
else:
pending.append(row)
if not pending:
print("Nothing to submit")
return
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
future_to_row = {
executor.submit(process_product, row, items.get(row["sku"])): row
for row in pending
}
for future in as_completed(future_to_row):
row = future_to_row[future]
try:
record = future.result()
except Exception as exc: # Preserve an unexpected worker failure.
references, _ = reference_data(row)
record = result_record(
row,
references,
"failed",
int(items.get(row["sku"], {}).get("attempt", 0)),
error=f"Unexpected worker error: {exc}",
)
items[row["sku"]] = record
manifest["prompt_version"] = PROMPT_VERSION
manifest["updated_at"] = now_utc()
save_manifest(MANIFEST_PATH, manifest)
print(f"{record['status'].upper()} {row['sku']}: {record.get('local_path') or record.get('error')}")
if __name__ == "__main__":
main()
Run it with:
python bulk_seedream_products.py
You can change the worker count and output size without editing the file:
MAX_WORKERS=2 SEEDREAM_SIZE=2048x2048 python bulk_seedream_products.py
The console reports each SKU as it finishes. Open manifest.json to see which files are downloaded, failed, or held for a manual check. If the live response uses a field shape not covered by output_items(), the script records the mismatch instead of silently treating the job as complete. Update that small parser using the JSON captured during the single-product test.
Review Every Image Before It Reaches the Store
A downloaded image is not an approved image. Keep generation status and approval status separate.
| Check |
Common failure |
| Geometry |
Changed cap, strap, button, handle, or proportions |
| Color |
Wrong shade, saturation, or surface finish |
| Material |
Plastic replaces metal; leather texture becomes flat |
| Packaging |
Invented logo, misspelled label, or changed volume |
| Scene |
Floating object or implausible contact shadow |
| Crop |
Product is clipped in marketplace thumbnails |
| Duplication |
An extra product or accessory appears |
Move a record from pending to approved, rejected, or needs_edit only after a person compares the output with the source images. If label copy must be legally exact, keep the source label in post-production rather than trusting generated text without verification.
For a first-party quality benchmark, run a small matrix before scaling:
a labeled bottle across a catalog background, lifestyle scene, and campaign layout;
a leather bag across the same three scene types; and
a lamp with geometry and control details visible.
Record first-pass approval rate, attempts per approved image, identity failures, label errors, and API cost per approved image. Those measurements describe your catalog better than a generic model demo.
Make the Workflow Usable by a Team
The manifest is the handoff contract between the operator, designer, and reviewer. Keep these fields even if one person currently does every job:
| Field |
Why it matters |
sku |
Permanent primary key for the product |
prompt_version |
Identifies the shared rules used for the output |
reference_urls |
Reconstructs the exact source set |
attempt |
Separates reruns and supports cost analysis |
requester and reviewer |
Assigns ownership |
approval_status |
Keeps generated and publishable assets distinct |
output_url and local_path |
Connects the API result to the stored file |
error |
Preserves the reason a row stopped |
| timestamps |
Shows when the record changed |
Treat the SKU as immutable. If the shared prompt changes, increment PROMPT_VERSION rather than overwriting the history. Rerun rejected rows, not the whole catalog. Store approved assets separately from drafts, and never commit an API key to the same repository.
This is the difference between a batch script and a team workflow. The script produces files. The records explain how those files were produced and whether they are allowed to ship.
Common Problems and Fixes
| Problem |
Likely cause |
Fix |
401 or 403 |
Key, header, account access, or balance |
Confirm the current Bearer header on the live model page and check the account |
| Reference does not load |
Private, local, expired, or non-HTTPS URL |
Use a stable public HTTPS URL and test it outside your signed-in browser |
429 |
Too many concurrent requests |
Lower MAX_WORKERS; honor the documented retry response |
| Request returns a task but no image |
Asynchronous mode is active |
Use enable_sync_mode: true for this script, or implement polling from a verified live response |
| Possible duplicate after timeout |
Client lost a response after submission |
Do not blindly retry; check account history and mark the row manually |
| Product changes too much |
Reference roles or protected details are vague |
Name the identity reference and list the visible parts that must remain |
| Extra objects or collage |
Deliverable is underspecified |
Ask for one product image and explicitly reject collages and duplicates |
| Label text is wrong |
Generative rendering is not exact |
Check every character; preserve exact regulated text in post-production |
| Output parser finds no image |
Current JSON shape differs |
Inspect the single-test response and update output_items() |
Do not add retries until you know whether the failure happened before or after the server accepted the request. Reliability is not the same as resubmitting everything.
When Seedream 5.0 Pro Is the Right Choice
Seedream 5.0 Pro fits product lifestyle scenes, campaign layouts, localized creative, and catalog variations guided by reference images. Its flat per-image tiers also make the raw generation budget straightforward to calculate.
It is not a substitute for product photography or post-production when the output must preserve pixel-identical geometry, legally exact packaging, or regulated label copy. It is also the wrong fit for a system that publishes every generated image without review, or for a requirement that one request return many separate product files.
Use it for scalable creative production. Keep a person between generation and publication.
Start with a Representative Catalog Slice
Choose a small set of SKUs that covers different shapes, materials, labels, and accessories. Lock the CSV schema and prompt version. Measure attempts, rejection reasons, and cost per approved image. Expand only after the resume and review flow works.
Review the current parameters and price on the Seedream 5.0 Pro API page, then run one product before funding a full catalog batch. For product references rather than text-only generation, open the Seedream 5.0 Pro Image Edit API.