
If you are searching for an image inpainting API, one fact changes how you write your code: the current generation of image-to-image models on hiapi does not take a mask. All four editing endpoints — flux-2/image-to-image, seedream-4.5/image-to-image, seedream-5.0-pro/image-to-image, and gpt-image-2/image-to-image — are instruction-based. You describe the edit in plain language ("remove the white van on the right, reconstruct the storefront behind it") and the model localizes the change itself. The schemas are strict: send a mask_url and the API answers 400 INVALID_REQUEST with additional properties 'mask_url' not allowed.
Every endpoint shares the same async lifecycle — POST /v1/tasks to submit, poll GET /v1/tasks/<id> (or receive a callback), then download output[0].url. What differs is the input schema, and each one below was validated against the live API:
| Model id | Required input fields | aspect_ratio values | Output size control |
|---|---|---|---|
flux-2/image-to-image | prompt, image_urls, aspect_ratio, resolution | 1:1, 4:3, 3:4, 16:9, 9:16, 3:2, 2:3, auto | resolution: 1K, 2K |
seedream-4.5/image-to-image | prompt, image_urls, aspect_ratio, resolution | 1:1, 4:3, 3:4, 16:9, 9:16, 2:3, 3:2, 21:9 | resolution: 2K, 4K |
seedream-5.0-pro/image-to-image | prompt, image_urls, aspect_ratio, quality | 1:1, 4:3, 3:4, 16:9, 9:16, 2:3, 3:2 | quality: basic, high |
gpt-image-2/image-to-image | prompt, input_urls | — (decided by the model) | — (decided by the model) |
Two gotchas hide in that table. First, gpt-image-2/image-to-image names its image field input_urls, not image_urls. Second, only flux-2/image-to-image accepts aspect_ratio: "auto", which preserves the source image's framing — exactly what you want for object removal, so it is the default model in this guide.
Goal: a small Python function that takes a source image URL plus an edit instruction, and returns the edited image bytes — usable for object removal, background cleanup, filling gaps, and local rewrites.
You need two things:
sk-... and goes in the Authorization: Bearer header.Submit an object-removal edit with flux-2:
curl -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer sk-<your-key>" \
-H "Content-Type: application/json" \
-d '{
"model": "flux-2/image-to-image",
"input": {
"prompt": "Remove the white delivery van parked on the right side of the street. Reconstruct the storefront and sidewalk that were behind it. Keep the lighting, colors, and everything else unchanged.",
"image_urls": ["https://your-cdn.example.com/street.jpg"],
"aspect_ratio": "auto",
"resolution": "2K"
}
}'
A successful submit returns a task id:
{"code": 200, "data": {"taskId": "task_abc123"}}
Then poll until the task reaches a terminal status (success or fail):
curl https://api.hiapi.ai/v1/tasks/task_abc123 \
-H "Authorization: Bearer sk-<your-key>"
On success, the edited image is at data.output[0].url. That URL is temporary (the response includes an expireAt) — download the bytes immediately and store them yourself; never hotlink it.
import os
import time
import requests
API = "https://api.hiapi.ai/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"}
def edit_image(model: str, input_payload: dict, timeout_s: int = 300) -> bytes:
"""Submit an image-to-image task, poll to a terminal state, return image bytes."""
# 1. Submit
resp = requests.post(
f"{API}/tasks",
json={"model": model, "input": input_payload},
headers=HEADERS,
timeout=60,
)
resp.raise_for_status()
task_id = resp.json()["data"]["taskId"]
# 2. Poll until terminal status: "success" or "fail"
deadline = time.time() + timeout_s
while time.time() < deadline:
task = requests.get(
f"{API}/tasks/{task_id}", headers=HEADERS, timeout=30
).json()["data"]
if task["status"] == "success":
# 3. Output URL expires — download right away
url = task["output"][0]["url"]
return requests.get(url, timeout=120).content
if task["status"] == "fail":
err = task.get("error") or {}
raise RuntimeError(f"task {task_id} failed: {err.get('code')} {err.get('message')}")
time.sleep(5)
raise TimeoutError(f"task {task_id} not finished after {timeout_s}s")
image_bytes = edit_image(
"flux-2/image-to-image",
{
"prompt": (
"Remove the white delivery van parked on the right side of the street. "
"Reconstruct the storefront and sidewalk that were behind it. "
"Keep the lighting, colors, and everything else unchanged."
),
"image_urls": ["https://your-cdn.example.com/street.jpg"],
"aspect_ratio": "auto",
"resolution": "2K",
},
)
with open("street-clean.png", "wb") as f:
f.write(image_bytes)
Run it with HIAPI_API_KEY=sk-... python edit.py. The only dependency is requests.
Since there is no mask, the prompt is your mask. The pattern that holds up across all four models has three parts:
The same pattern covers the neighboring inpainting jobs:
Each call returns one output image, so for heavy cleanup you can also chain edits: run one removal, download the result, re-host it, and feed that URL into the next call. (Re-hosting matters — output URLs expire, so don't pass them directly as the next task's input.)
The edit_image function above works unchanged for the other three endpoints — only the payload differs:
# Seedream 5.0 Pro: quality tiers instead of resolution, up to 10 reference images
edit_image("seedream-5.0-pro/image-to-image", {
"prompt": "Remove the tourists from the plaza. Extend the paving stones. Keep everything else unchanged.",
"image_urls": ["https://your-cdn.example.com/plaza.jpg"],
"aspect_ratio": "3:2",
"quality": "high", # "basic" | "high" — this model has no "resolution" field
})
# Seedream 4.5: when you need 4K output
edit_image("seedream-4.5/image-to-image", {
"prompt": "Remove the scaffolding from the building facade. Reconstruct the brickwork behind it. Keep everything else unchanged.",
"image_urls": ["https://your-cdn.example.com/facade.jpg"],
"aspect_ratio": "3:2",
"resolution": "4K", # "2K" | "4K" on this model (no "1K")
})
# GPT Image 2: simplest schema — note the field is input_urls
edit_image("gpt-image-2/image-to-image", {
"prompt": "Remove the person walking through the frame on the left. Fill with the beach background. Keep everything else unchanged.",
"input_urls": ["https://your-cdn.example.com/beach.jpg"],
})
These schemas are strict — unknown fields are rejected rather than ignored. That is a feature for debugging: a typo like image_url (singular) fails loudly at submit time with a per-field error message instead of silently producing a wrong result. But it also means you should not blind-copy an input payload from one model to another: sending flux-2's resolution to seedream-5.0-pro/image-to-image is a guaranteed 400.
Callbacks instead of polling. For anything beyond a script, pass a callback at submit time — hiapi POSTs the task result to your URL once, when it reaches success or fail:
{
"model": "flux-2/image-to-image",
"input": { "...": "..." },
"callback": { "url": "https://yourapp.com/hooks/hiapi", "when": "final" }
}
Polling is fine for CLIs and batch jobs (3–5 s interval is plenty); callbacks are better for user-facing apps and serverless backends where you don't want a worker occupying a slot just to wait. In your callback handler, download the output file immediately — the URL will have expired by the time a user clicks it later.
Persist the taskId before you poll. Submitting is the billable event. If your process crashes after submit, a naive retry re-submits and pays twice. Store the taskId (and your own request id) as soon as the submit response arrives, and on restart resume polling instead of re-creating the task.
Error handling. The two errors you'll actually see:
401 permission_denied — the key is missing, malformed, or not allowed to use this model. Check the Authorization: Bearer sk-... header and the key's permissions in the dashboard.400 INVALID_REQUEST — the input failed schema validation. The message lists every failing field at once (e.g. resolution: value must be one of '1K', '2K'), so you can fix a payload in one iteration.A fail status after submit (rather than an HTTP error at submit) usually means the source image URL wasn't fetchable or the content was rejected — the task's error.code and error.message say which.
Does the hiapi inpainting API support masks?
No. All four image-to-image endpoints reject mask fields (400: additional properties 'mask_url' not allowed). Precision comes from prompt specificity instead: name the object, its location, what replaces it, and pin the rest of the image. In practice this removes an entire step from your pipeline — no mask drawing UI, no segmentation model.
Which model is best for object removal?
Start with flux-2/image-to-image: it is the only endpoint with aspect_ratio: "auto", so the output keeps your source framing. Use seedream-4.5/image-to-image when you need 4K output, seedream-5.0-pro/image-to-image when the edit needs multiple reference images (up to 10), and gpt-image-2/image-to-image when you want the simplest possible payload. Per-image prices differ by model and tier — compare them on the pricing page.
Can I remove several objects in one API call? Yes — list them in one prompt ("remove the trash bin and the two traffic cones..."). Each call produces one output image. For iterative cleanup, chain calls: download each result, re-host it on your own storage, and use that URL as the next call's input. Don't feed a task's output URL directly into the next task — those URLs expire.
How long does an inpainting task take?
Typically from a few seconds up to about a minute, depending on model and output size — higher resolutions and quality tiers take longer. Poll every 3–5 seconds, or skip polling entirely with callback: {"url": ..., "when": "final"}.
Why does my request fail with a 400 before any image is generated?
The task API validates input against a strict per-model schema at submit time. Common causes: using image_urls with gpt-image-2/image-to-image (it wants input_urls), sending resolution to seedream-5.0-pro/image-to-image (it uses quality), an aspect ratio outside the model's allowed list, or a prompt shorter than 3 characters. The error message names each offending field, so fixes are usually one-line.
Can I use inpainting output commercially? Generated output follows your plan's usage terms. Make sure you hold the rights to the source images you edit — removing objects from photos you don't own doesn't change the underlying copyright.
Key Takeaways