Building an Image Workflow with Seedream: A Practical API Guide
Generate, edit, and run asynchronous image jobs with one carefully structured API workflow.

Image APIs are easy to demo and surprisingly easy to integrate badly; this guide builds a small Seedream workflow that stays explicit about models, output modes, and failure handling.

What we are building
We will make one request to Ace Data Cloud's Seedream endpoint, read the returned image URL, and then look at two production concerns: editing an existing image and moving long-running work off the request path.
The API endpoint is:
POST https://api.acedata.cloud/seedream/images
Every request uses Authorization: Bearer ${token} and a JSON body. The most important decision is the full model string. For the examples below, I use doubao-seedream-5-0-lite-260128. Do not shorten it to a friendly alias such as doubao-seedream-5.0-lite; the API expects the full identifier and an abbreviation returns HTTP 400.
Seedream 5.0 Lite is a practical default when you want more than a single still. It supports image groups through sequential_image_generation, streaming through stream, and the web_search tool. Seedream 5.0 Pro, identified as doubao-seedream-5-0-pro-260628, is the flagship single-image model, but it does not support those three capabilities. Choosing a model should therefore come before designing the rest of the job payload.
Start with a synchronous generation request
Keep the first request deliberately small. A prompt, an action, and a model are enough to validate authentication and response parsing:
curl -X POST 'https://api.acedata.cloud/seedream/images' \
-H 'accept: application/json' \
-H 'authorization: Bearer ${token}' \
-H 'content-type: application/json' \
-d '{
"action": "generate",
"model": "doubao-seedream-5-0-lite-260128",
"prompt": "A single matte blue cube centered on a clean white studio background, neutral lighting"
}'
A successful response includes success, task_id, trace_id, and a data array. Each generated result in data carries its prompt, size, and image_url. In application code, check success before reading data[0].image_url, and retain trace_id in your logs. That last detail saves time when a request crosses several internal services.
The size field has two styles, and they should not be mixed. You can pass a model-supported resolution preset and describe the aspect ratio in the prompt, or provide explicit dimensions such as 2048x2048. The supported presets differ by model: 5.0 Lite accepts 2K, 3K, and 4K, while 5.0 Pro accepts 1K, 1.5K, and 2K. Treat size as model-specific validation rather than a universal enum.
Two output switches are worth deciding early. response_format defaults to url and can also be b64_json. watermark defaults to true. For 5.0 Lite and 5.0 Pro, output_format can be jpeg or png, with jpeg as the default.
Turn generation into image editing
Editing uses the same endpoint. Instead of inventing a separate client path, add image to the request and describe only the desired transformation in prompt. The input can be a URL or Base64 data, and the supported models accept one or more input images.
{
"model": "doubao-seedream-4-0-250828",
"prompt": "Keep the subject and composition unchanged. Replace the metallic material with transparent glass and preserve the lighting direction.",
"image": ["https://example.com/input.png"],
"size": "2K",
"watermark": false
}
This pattern works well for controlled creative tools because the caller owns the invariant: what must remain unchanged belongs in the prompt. The service returns the edited asset through the same data[].image_url shape, so generation and editing can share one response handler.
If you need compositing rather than a flattened edit, Seedream 5.0 Pro also exposes layer_decomposition. With layer_decomposition: true, one PNG or JPEG input can be split into a base image plus up to 16 transparent PNG layers. Returned layers are ordered from bottom to top by z_index and include name, description, and absolute and normalized bounding boxes. This mode cannot be combined with image groups, streaming, web search, or background, so keep it as a separate workflow branch.
Choose between streaming, callbacks, and polling
Image work may take roughly one to two minutes. Holding every application request open for that entire period is rarely the cleanest architecture.
For Lite and 4.x models, stream: true enables newline-delimited JSON when the request header uses accept: application/x-ndjson. The stream emits image_generation.partial_succeeded or image_generation.partial_failed events and ends with one image_generation.completed event plus final usage. Streaming cannot be combined with async or callback_url.
For background work, there are two alternatives:
- Add
callback_urland correlate the later POST payload with the returnedtask_id. - Set
async: truewithout a callback, then poll/seedream/taskswith thattask_id.
I prefer callbacks for server-to-server jobs and polling for local scripts or environments without a public receiver. Either way, store both task_id and trace_id; one identifies the work, while the other helps diagnose the request.
Handle failures as part of the interface
The documented errors are concrete enough to map into application behavior. A 401 invalid_token should stop the job and trigger credential handling. A 429 too_many_requests should enter bounded retry with backoff. HTTP 400 responses such as token_mismatched or api_not_implemented usually point to missing, invalid, or unsupported parameters. A 500 api_error is a server-side failure; log the returned trace_id and avoid retrying forever.
The small builder lesson here is not “add AI.” It is to make model constraints and job state visible in your own system. Start with one synchronous request, unify generation and editing responses, and introduce asynchronous execution only when the user experience requires it. The full field reference and current model constraints are in the Seedream Images API integration document.






