
POST /v1/tasks, poll GET /v1/tasks/<taskId> (or use a callback), download the edited image from output[0].url.prompt and image are both required. image takes one string URL, not an array — this model edits a single reference image per call, unlike some other hiapi image models that accept image_urls lists. Three optional knobs: aspect_ratio (14 ratios), resolution (1k | 2k), quality (low | medium). Anything else is rejected with a 400.resolution/quality — check the pricing page before you commit to a tier in production.expireAt timestamp) — download or re-upload to your own storage right after the task completes.error.code: "permission_denied".Goal: send a source image URL plus an edit instruction to grok-imagine-image-2.0/image-to-image and get back a finished image URL — first with curl, then as a small Python script.
You need two things: a hiapi API key, and a publicly reachable URL for the image you want to edit (the platform fetches it server-side — a local file path won't work). Grab the key from your hiapi dashboard and export it:
export HIAPI_API_KEY="sk-..."
Every request authenticates with Authorization: Bearer sk-<key>.
Image editing on hiapi is async-only — every model, including grok-imagine-image-2.0/image-to-image, runs behind the same unified task API. You create a task, then fetch the result once it's done.
Step 1 — create the task:
curl -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "grok-imagine-image-2.0/image-to-image",
"input": {
"prompt": "Change the mug on the table to matte black ceramic, keep everything else in the scene identical",
"image": "https://your-cdn.example.com/source-photo.jpg",
"aspect_ratio": "1:1",
"resolution": "2k"
}
}'
You get a taskId back immediately:
{
"code": 200,
"message": "success",
"data": { "taskId": "tk-hiapi-01M01JW1C9N3VHRZAEBFEESC5Q" }
}
Step 2 — poll until the task reaches a terminal state:
curl https://api.hiapi.ai/v1/tasks/tk-hiapi-01M01JW1C9N3VHRZAEBFEESC5Q \
-H "Authorization: Bearer $HIAPI_API_KEY"
status moves from handling to success (or fail). On success you get the image:
{
"code": 200,
"data": {
"taskId": "tk-hiapi-01M01JW1C9N3VHRZAEBFEESC5Q",
"status": "success",
"storage": "temp",
"output": [
{
"type": "image",
"url": "https://temp.hiapi.ai/7c6ttvrbpt/01M01JW1C9N3VHRZAEBFEESC5Q-0.jpg",
"artifactId": "76711",
"expireAt": 1787364206
}
]
},
"message": "success"
}
storage: "temp" and expireAt matter here — the URL is not permanent. Download it (or push it into your own object storage) as soon as the task finishes.
input only accepts these fields — anything else fails schema validation with a 400:
| Field | Required | Type | Notes |
|---|---|---|---|
prompt | yes | string | The edit instruction |
image | yes | string | A single public image URL — not an array |
aspect_ratio | no | enum | 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3, 2:1, 1:2, 19.5:9, 9:19.5, 20:9, 9:20, auto |
resolution | no | enum | 1k, 2k |
quality | no | enum | low, medium |
There's no image_urls, n, size, strength, mask, seed, or negative_prompt on this model — the API returns additional properties '<field>' not allowed for any of those. If you're coming from a model that takes multiple reference images (several hiapi models do), note the singular image field here: grok-imagine-image-2.0/image-to-image edits exactly one source image per call.
import os
import time
import requests
API_KEY = os.environ["HIAPI_API_KEY"]
BASE = "https://api.hiapi.ai/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
def edit_image(prompt: str, image_url: str, aspect_ratio: str = "1:1", resolution: str = "1k") -> str:
create = requests.post(
f"{BASE}/tasks",
headers=HEADERS,
json={
"model": "grok-imagine-image-2.0/image-to-image",
"input": {
"prompt": prompt,
"image": image_url,
"aspect_ratio": aspect_ratio,
"resolution": resolution,
},
},
timeout=30,
)
create.raise_for_status()
task_id = create.json()["data"]["taskId"]
while True:
poll = requests.get(f"{BASE}/tasks/{task_id}", headers=HEADERS, timeout=30)
poll.raise_for_status()
data = poll.json()["data"]
if data["status"] == "success":
return data["output"][0]["url"]
if data["status"] == "fail":
raise RuntimeError(f"task {task_id} failed: {data}")
time.sleep(2)
if __name__ == "__main__":
url = edit_image(
"Change the mug on the table to matte black ceramic, keep everything else identical",
"https://your-cdn.example.com/source-photo.jpg",
)
print(url)
Use a callback instead of polling. For anything beyond a quick script, attach a callback to the task creation call and let hiapi push the result to your own endpoint when it's ready, instead of polling in a tight loop:
{
"model": "grok-imagine-image-2.0/image-to-image",
"input": { "prompt": "...", "image": "https://your-cdn.example.com/source-photo.jpg" },
"callback": { "url": "https://your-service.example.com/hooks/hiapi", "when": "final" }
}
when: "final" fires exactly once, when the task reaches success or fail — no partial/progress events to filter out. Use polling for short interactive flows and callbacks for anything running as a batch job.
Host the source image somewhere reachable before you call the API. If your source photo is a user upload sitting in a private bucket or on localhost, the platform can't fetch it and the task will fail asynchronously (you'll see a terminal fail status, not a synchronous 400) rather than at request time — so validate the URL is actually public before you submit the task, not after.
Persist output immediately. storage: "temp" output expires at expireAt — treat the URL as a pointer you consume once, then re-upload the bytes to your own storage (S3, R2, GCS) for anything that needs to outlive that window.
Idempotency. The task API doesn't take a client-supplied idempotency key — if a request to POST /v1/tasks times out on your end mid-flight, you can't safely assume it didn't create a task. Keep your own record of taskId per logical job before you consider the create call "sent", and treat a duplicate task as a state your retry logic should detect and clean up.
Error handling. A missing or invalid key returns HTTP 401 with a JSON body like:
{
"error": {
"code": "permission_denied",
"message": "This API key cannot use the selected model. Please check permissions or use another key.",
"request_id": "..."
}
}
Schema violations (missing prompt/image, an unknown field, an out-of-enum value) come back as HTTP 400 with error_code: "INVALID_REQUEST" and a message that names the offending field — cheap to handle: log the message and don't retry, since retrying an invalid request just repeats the same 400. A source image the platform can't fetch or can't edit surfaces later, as a terminal fail status on the task instead.
Does image take one URL or a list?
One string URL. grok-imagine-image-2.0/image-to-image edits a single source image per call — send an array and you'll get image: got array, want string. If you need to compose multiple references into one output, check other hiapi models that expose image_urls.
Why did my request fail with "additional properties not allowed"?
The input schema is strict — only prompt, image, aspect_ratio, resolution, and quality are accepted. Fields other image-editing models support (strength, mask, seed, negative_prompt) aren't part of this model's schema and fail validation immediately.
My source image URL is valid in a browser — why does the task still fail?
The platform fetches server-side, so anything behind auth, a signed-URL expiry, or localhost won't resolve. Use a public, long-lived URL (your own CDN/object storage) as the image value.
Can I edit multiple images in one call?
No — one image in, one edited image out. Issue one task per image; run them concurrently client-side if you need a batch.
How long is the output URL valid?
It's temporary storage with an expireAt Unix timestamp in the response. Download or re-host the image right after the task succeeds rather than storing the URL long-term.
Do I need to poll, or can I just wait synchronously?
The create call returns as soon as the task is queued — it doesn't block until the edit is ready. Poll GET /v1/tasks/<taskId> for short-lived scripts, or set a callback for production so you're not holding a connection open.
Key Takeaways