TL;DR
- What this is: a working recipe for calling the flux-2/image-to-image API on hiapi — create a task, poll it, download the result.
- Endpoint:
POST https://api.hiapi.ai/v1/taskswith"model": "flux-2/image-to-image", thenGET /v1/tasks/<taskId>until it reaches a terminal status. - Required input fields (all four):
prompt,image_urls(1–8 public URLs — this is the Multi-Reference feature),aspect_ratio, andresolution(1Kor2K). - The schema is strict. Fields like
strengthorfidelitydon't exist and are rejected with a 400. Fidelity is controlled by your reference images, your prompt, andresolution— not by a separate knob.
What you're building, and what you need
Goal: send one or more reference images plus a text prompt to flux-2, and get back an edited/restyled image you can download — from curl or Python, with no SDK.
Prerequisites:
- A hiapi API key — create one in the hiapi dashboard. Keys look like
sk-...and go in theAuthorization: Bearerheader. - At least one publicly reachable image URL to use as a reference. The API fetches
image_urlsserver-side, solocalhostlinks or private-bucket URLs won't work — use a public bucket, CDN, or presigned URL. curl, or Python 3.8+ withrequests(pip install requests).
The request schema
flux-2/image-to-image runs on hiapi's unified task interface. The input object takes exactly these fields:
| Field | Type | Required | Notes |
|---|---|---|---|
prompt | string | yes | What to change or generate. Minimum 3 characters. |
image_urls | string[] | yes | 1–8 public image URLs. More than one = Multi-Reference: flux-2 blends identity, product, or style cues across all of them. |
aspect_ratio | string | yes | One of 1:1, 4:3, 3:4, 16:9, 9:16, 3:2, 2:3, or auto (follow the reference image). |
resolution | string | yes | 1K or 2K. |
Anything else is rejected. For example, sending a strength or fidelity field returns:
{
"code": 400,
"error_code": "INVALID_REQUEST",
"message": "invalid input: <root>: additional properties 'fidelity', 'strength' not allowed"
}
That's worth internalizing: flux-2's image-to-image mode has no denoise-strength dial. How faithful the output stays to your references ("high fidelity" editing vs. loose restyling) is driven by how you write the prompt — e.g. "keep the product, its label and proportions exactly as in the reference; only replace the background" — plus resolution: "2K" for detail-critical work.
Minimal working example: curl
Step 1 — create the task:
curl -s -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": "Place this product on a white marble countertop with soft morning light, keep the product itself unchanged",
"image_urls": ["https://your-cdn.example.com/product.jpg"],
"aspect_ratio": "1:1",
"resolution": "2K"
}
}'
A successful create returns a task ID in data.taskId.
Step 2 — poll until it finishes:
curl -s https://api.hiapi.ai/v1/tasks/TASK_ID \
-H "Authorization: Bearer sk-YOUR_KEY"
Keep polling every few seconds while data.status is non-terminal. On "status": "success", the image URL is at data.output[0].url.
Step 3 — download the result immediately:
curl -s -o result.jpg "OUTPUT_URL"
Output URLs carry an expireAt timestamp — they are temporary. Download the bytes (or copy them to your own storage) as soon as the task succeeds; don't hot-link the task output in production.
Minimal working example: Python
A complete script — create, poll, download — with nothing but requests:
import time
import requests
API_KEY = "sk-YOUR_KEY"
BASE = "https://api.hiapi.ai/v1/tasks"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
def create_task() -> str:
resp = requests.post(BASE, headers=HEADERS, json={
"model": "flux-2/image-to-image",
"input": {
"prompt": ("Combine the person from the first image with the jacket "
"from the second image, studio lighting, neutral backdrop"),
"image_urls": [
"https://your-cdn.example.com/person.jpg",
"https://your-cdn.example.com/jacket.jpg",
],
"aspect_ratio": "3:4",
"resolution": "2K",
},
}, timeout=30)
body = resp.json()
task_id = (body.get("data") or {}).get("taskId")
if not task_id:
raise RuntimeError(f"create failed: {body}")
return task_id
def wait_task(task_id: str, timeout_s: int = 600, poll_s: int = 5) -> dict:
deadline = time.time() + timeout_s
while time.time() < deadline:
task = requests.get(f"{BASE}/{task_id}", headers=HEADERS,
timeout=30).json().get("data") or {}
status = task.get("status")
if status == "success":
return task
if status == "fail":
err = task.get("error") or {}
raise RuntimeError(f"task failed: {err.get('code')} {err.get('message')}")
time.sleep(poll_s)
raise TimeoutError(f"task {task_id} still running after {timeout_s}s")
task_id = create_task()
print("task:", task_id)
task = wait_task(task_id)
url = task["output"][0]["url"] # temporary URL — save it now
img = requests.get(url, timeout=120).content
with open("result.jpg", "wb") as f:
f.write(img)
print("saved result.jpg,", len(img), "bytes")
Note the two-image image_urls — that's Multi-Reference in practice. Up to 8 references are accepted; the model composes across them (person + garment, product + background style, character + pose reference, and so on).
Production notes
Callbacks instead of polling. For anything beyond a script, register a callback when you create the task so hiapi pushes the terminal result to you:
{
"model": "flux-2/image-to-image",
"input": { "...": "..." },
"callback": { "url": "https://your-app.example.com/hooks/hiapi", "when": "final" }
}
With "when": "final" you get exactly one POST when the task reaches a terminal state. Rule of thumb: polling is fine for CLIs, batch jobs, and anything short-lived; callbacks are better for user-facing apps where a worker holding a poll loop per image doesn't scale. If you can't expose a public callback endpoint, poll — just back off to 5s+ intervals.
Make retries safe. Treat task creation as the side effect it is: store the returned taskId against your own job record before acting on the result, and on retry, re-check the stored task's status instead of blindly creating a new task (each successful create bills separately). If your job runner may fire twice, dedupe on your own job ID first.
Handle the errors you'll actually see:
-
401
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. ...", "type": "hiapi_error"}}Check the key in your dashboard; our invalid API key checklist covers the usual causes.
-
400
INVALID_REQUEST— schema violation. Themessagenames every offending field at once (missing required fields, bad enum values, unknown extras), so read it fully rather than fixing one field per retry. -
Task
status: "fail"— the task was accepted but generation failed; the error is insidedata.error. Handle it in your poll/callback path, not at create time. -
Expired output URL — if a download 4xx's minutes after success, you waited too long. Re-check
expireAtand always download immediately.
Related reading
- flux-2/image-to-image model page — parameters and current pricing
- flux-2/text-to-image model page — same family, no reference images
- flux-2 for e-commerce product images — an end-to-end recipe built on the text-to-image side
- FLUX 1.1 Pro on hiapi — the previous-generation option compared
- hiapi pricing — per-image cost across resolutions
FAQ
How many reference images can I pass to flux-2 image-to-image?
Between 1 and 8, as public URLs in image_urls. Passing more than 8 fails validation (maxItems: got N, want 8).
Is there a strength or fidelity parameter?
No. The input schema accepts only prompt, image_urls, aspect_ratio, and resolution; unknown fields are rejected with a 400. Control fidelity through prompt phrasing (state explicitly what must stay unchanged) and use 2K when detail matters.
What resolutions and aspect ratios are supported?
resolution is 1K or 2K. aspect_ratio is one of 1:1, 4:3, 3:4, 16:9, 9:16, 3:2, 2:3, or auto — auto follows your reference image, which is usually what you want for edits.
Can I send base64 images instead of URLs?
No — image_urls takes URLs, and they must be publicly reachable so the API can fetch them. Upload to a bucket or CDN first and pass the resulting URL (presigned URLs work).
How much does flux-2 image-to-image cost per image? Pricing varies by resolution — check the flux-2/image-to-image model page and the pricing page for current per-image rates.
How long do generated image URLs stay valid?
Output URLs are temporary and include an expireAt field. Download the image bytes as soon as the task succeeds and store them yourself.
What's the difference between flux-2/image-to-image and flux-2/text-to-image? Same model family, different modes: text-to-image generates from a prompt alone, while image-to-image requires 1–8 reference images and edits or composes from them. Their input schemas differ, so the request bodies aren't interchangeable.





