
Editing an image with seedream-4.5 is one POST to hiapi's unified task API, a short poll, and a download. This guide gives you a working curl request and a complete Python script for seedream-4.5/image-to-image, plus the exact input schema (verified against the live API), multi-reference usage, and the errors you'll actually hit.
Goal: send one or more source images plus an edit instruction to seedream-4.5/image-to-image, and get back a finished image URL.
Prerequisites:
The model runs on the async task endpoint: POST /v1/tasks creates a task, GET /v1/tasks/<taskId> reports status, and the finished image appears at data.output[0].url.
seedream-4.5/image-to-image has four required input fields and accepts nothing else — the schema is strict, and unknown fields are rejected with a 400:
| Field | Type | Constraints |
|---|---|---|
prompt | string | The edit instruction |
image_urls | array of strings | Public URLs, max 14 images |
aspect_ratio | string | One of 1:1, 4:3, 3:4, 16:9, 9:16, 2:3, 3:2, 21:9 |
resolution | string | 2K or 4K only |
Two things worth flagging: there is no n parameter (one output per task — fan out parallel tasks if you need variants), and unlike seedream-5.0-pro's image-to-image (which takes 1K/2K), 4.5 wants 2K or 4K. Sending 1K here returns a validation error.
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer sk-<your-key>" \
-H "Content-Type: application/json" \
-d '{
"model": "seedream-4.5/image-to-image",
"input": {
"prompt": "Turn this product shot into a clean studio photo on a seamless white background with soft shadows",
"image_urls": ["https://your-cdn.example.com/source.jpg"],
"aspect_ratio": "1:1",
"resolution": "2K"
}
}'
A successful create returns a task ID at data.taskId. Poll it:
curl -s https://api.hiapi.ai/v1/tasks/<taskId> \
-H "Authorization: Bearer sk-<your-key>"
When data.status reaches success, the image URL is at data.output[0].url. On fail, the reason is in data.error.
No SDK needed — requests and a polling loop:
import time
import requests
API_BASE = "https://api.hiapi.ai/v1/tasks"
API_KEY = "sk-<your-key>"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
def edit_image(prompt: str, image_urls: list[str],
aspect_ratio: str = "1:1", resolution: str = "2K") -> bytes:
# 1. Create the task
resp = requests.post(API_BASE, headers=HEADERS, json={
"model": "seedream-4.5/image-to-image",
"input": {
"prompt": prompt,
"image_urls": image_urls, # public URLs, max 14
"aspect_ratio": aspect_ratio,
"resolution": resolution, # "2K" or "4K"
},
}, timeout=60)
resp.raise_for_status()
task_id = resp.json()["data"]["taskId"]
# 2. Poll until terminal state
deadline = time.time() + 600
while time.time() < deadline:
task = requests.get(f"{API_BASE}/{task_id}",
headers=HEADERS, timeout=30).json()["data"]
if task["status"] == "success":
url = task["output"][0]["url"]
# 3. Output URLs expire -- download immediately
return requests.get(url, timeout=120).content
if task["status"] == "fail":
err = task.get("error") or {}
raise RuntimeError(f"task failed: {err.get('code')} {err.get('message')}")
time.sleep(5)
raise TimeoutError(f"task {task_id} did not finish in 600s")
png = edit_image(
"Turn this product shot into a clean studio photo on a seamless white background",
["https://your-cdn.example.com/source.jpg"],
)
with open("edited.png", "wb") as f:
f.write(png)
Note the comment on step 3: output URLs carry an expiry timestamp. Persist the bytes to your own storage as soon as the task succeeds — don't store the returned URL and expect it to work tomorrow.
image_urls accepts up to 14 images in one task. That's the lever for style transfer and composition jobs: pass the image you want edited plus reference images, and describe how to combine them in the prompt.
{
"model": "seedream-4.5/image-to-image",
"input": {
"prompt": "Apply the color grading and lighting style of the second image to the first image, keep the subject unchanged",
"image_urls": [
"https://your-cdn.example.com/subject.jpg",
"https://your-cdn.example.com/style-reference.jpg"
],
"aspect_ratio": "3:2",
"resolution": "2K"
}
}
Order matters to the prompt, not the API — refer to images by position ("the first image", "the second image") so the model knows which is which.
Callbacks instead of polling. For anything beyond a script, add a root-level callback object when creating the task and skip the polling loop entirely:
{
"model": "seedream-4.5/image-to-image",
"input": { "...": "..." },
"callback": { "url": "https://yourapp.com/hooks/hiapi", "when": "final" }
}
hiapi POSTs the terminal task state to your URL when the task finishes. Polling is fine for CLI tools and notebooks; callbacks are the right call for web backends where you'd otherwise hold a worker open for the ~tens of seconds a 2K edit takes.
Idempotency. Task creation is not idempotent — retrying a create after a network timeout can produce (and bill) two tasks. Store the taskId the moment you get it, and on retry check whether you already have a live task before creating another.
Error handling. The two failures you'll see first:
permission_denied — bad key, or the key lacks access to this model. The body looks like {"error": {"code": "permission_denied", "message": "This API key cannot use the selected model...", "request_id": "..."}}. Include the request_id if you contact support.INVALID_REQUEST — schema violations, with a precise message: wrong resolution value, more than 14 image_urls, an unknown field, or image_urls not being an array. These are all caught before the task runs, so they cost nothing.Failed tasks report status: "fail" with an error object on the task itself — handle that as a distinct case from HTTP errors, as the Python script above does.
Per-image cost depends on resolution. Check the current rate on the pricing page. Requests rejected at validation (400) never start a task, so schema mistakes are free to make while you're integrating.
Can I pass a local file or base64 image instead of a URL?
No. image_urls must be an array of publicly reachable URLs — the platform fetches and normalizes them server-side. Upload to your own storage (S3, R2, any CDN) first, then pass the URL.
How many reference images does seedream-4.5 image-to-image support?
Up to 14 per task. Exceeding that returns a 400 with image_urls: maxItems in the message.
Why do I get a 400 when I set resolution to 1K?
The 4.5 image-to-image endpoint only accepts 2K or 4K. The 1K option belongs to seedream-5.0-pro's image-to-image — the enums differ between family tiers.
Can I generate multiple variants in one request?
No — the schema has no n parameter and rejects unknown fields. Create one task per variant and run them in parallel; each task returns exactly one output.
How long do result URLs stay valid? Output URLs are signed with an expiry. Download the image as soon as the task succeeds and store it yourself rather than hotlinking the task output.
What's the difference between an HTTP error and a failed task?
HTTP errors (400/401) mean the request never became a task — fix the payload or key and resend. A task that ends with status: "fail" was accepted but couldn't complete; the reason is in the task's error object, and retrying with the same input is usually the right move for transient failures.