HiAPI
Blog

Tutorial

How to Call gpt-image-2/image-to-image on HiAPI: curl, Python, and a Working Request

A developer walkthrough for image-to-image requests: submitting a reference image, polling the task, and downloading the result

Oct 31, 2026 8 min read
扁平流体形态交叠的封面图,墨黑底色上浮现青瓷绿与石板灰的克制波纹,呼应教程文章。

What the image-to-image endpoint expects

The gpt-image-2/image-to-image model on HiAPI is an asynchronous image generation endpoint. Unlike a synchronous call that blocks until an image comes back, this endpoint accepts your request, queues it, and hands you back a task handle. You then track that task until it finishes.

At the request level you provide three things:

  • The model id: gpt-image-2/image-to-image.
  • A reference image: the source image the model should transform or build from. This is the defining input of the image-to-image mode — without it, you are doing text-to-image.
  • A prompt and options: a text instruction describing the desired output, plus any optional fields the model documents (for example size or output format). Only send parameters the model documentation lists.

The response is not the image itself. It is a task object with an identifier and a status. Treat that status as the source of truth: pending or processing means keep waiting, succeeded means the result is ready, and failed means something went wrong and you should stop polling.

Design your client around this lifecycle from the start. Everything else in this walkthrough — curl, Python, polling, download — follows from the fact that submission and completion are two separate moments.

Prepare your HiAPI key and reference image

Before writing any code, gather two things.

An API key. Create one in the HiAPI console and store it in an environment variable rather than pasting it into scripts:

export HIAPI_API_KEY="your-key-here"

Treat the key as a secret. Do not commit it, and do not log it in request traces.

A reference image. Pick a local image file you want to transform. Keep it reasonably sized — a large, high-resolution source is the most common cause of slow or rejected requests. A PNG or JPEG in the same directory as your script is easiest for a first run:

ls -lh ./reference.png

Note the file path; you will reference it in both the curl and Python examples. If you plan to call the model from a server, confirm the process has read access to that path and that the file is a valid image (not a renamed file with the wrong extension).

With a key exported and an image on disk, you have everything the first request needs.

A working curl request

The fastest way to validate credentials and payload shape is a single curl call. This submits the task and prints the task object:

curl -sS https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-image-2/image-to-image",
    "input": {
      "image": "https://example.com/reference.png",
      "prompt": "Turn this photo into a watercolor illustration"
    }
  }'

Two notes on this example. First, the reference image is passed as a URL in the input.image field; if the endpoint documentation supports inline base64 or multipart upload for your use case, use that form instead, but keep the field name consistent. Second, the prompt is where you describe the transformation — be specific about style, composition, and what should stay the same.

Run it and read the response. A successful submission returns a task id and a non-terminal status. If you get a 401, your key is missing or wrong. If you get a 422 or similar validation error, inspect the payload — usually the model id or the image field is malformed.

Save the task id. The next steps depend on it.

Doing the same in Python

The Python version should mirror the curl call exactly, with auth and payload construction kept explicit so nothing is hidden behind a wrapper. Using requests:

import os
import requests

API_KEY = os.environ["HIAPI_API_KEY"]
BASE_URL = "https://api.hiapi.ai/v1"

headers = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

payload = {
    "model": "gpt-image-2/image-to-image",
    "input": {
        "image": "https://example.com/reference.png",
        "prompt": "Turn this photo into a watercolor illustration",
    },
}

response = requests.post(f"{BASE_URL}/tasks", json=payload, headers=headers, timeout=30)
response.raise_for_status()
task = response.json()
print(task["id"], task["status"])

If your integration sends local files instead of URLs, encode and attach the image per the endpoint documentation rather than inventing a field. The important discipline is the same as in curl: build the payload as a plain dictionary, send it, and check the HTTP status before parsing.

Wrap the submission in a function that returns the task id. That keeps the polling code in the next section independent of how the task was created.

Polling the async task to completion

The submit call returned while the image was still being generated. To get the result, poll the task status endpoint until the task reaches a terminal state. A robust loop has three properties: a delay between polls, a maximum wait, and clear handling of the failure state.

import time

def wait_for_task(task_id, headers, base_url, timeout=300, interval=3):
    deadline = time.time() + timeout
    while time.time() < deadline:
        r = requests.get(f"{base_url}/tasks/{task_id}", headers=headers, timeout=30)
        r.raise_for_status()
        task = r.json()
        status = task["status"]
        if status == "succeeded":
            return task
        if status == "failed":
            raise RuntimeError(f"Task failed: {task}")
        time.sleep(interval)
    raise TimeoutError(f"Task {task_id} did not finish within {timeout}s")

A few practical refinements:

  • Backoff: start with a short interval and increase it gradually, or keep a fixed interval that is generous enough not to hammer the API. Image generation is not instantaneous.
  • Timeout: always cap total wait time. Without it, a stuck task hangs your worker indefinitely.
  • Terminal states: treat only succeeded and failed as terminal. Any other status means keep polling. If the API adds intermediate states later, your loop still behaves correctly.

If you are running many tasks, poll in a background worker or queue rather than blocking a web request. The lifecycle is the same; only the concurrency model changes.

Downloading and verifying the result

Once the task reports success, the task object contains the location of the generated image — typically a URL in the output field. Download it and write it to disk:

task = wait_for_task(task_id, headers, BASE_URL)
image_url = task["output"]["image"]

img = requests.get(image_url, timeout=60)
img.raise_for_status()

with open("result.png", "wb") as f:
    f.write(img.content)

If the signed URL has an expiry, download promptly after completion or persist the file immediately rather than storing the URL for later. If your endpoint returns base64 instead of a URL, decode it before writing.

A quick sanity check prevents confusing downstream failures:

from PIL import Image

with Image.open("result.png") as im:
    im.verify()
print("valid image, bytes:", os.path.getsize("result.png"))

verify() raises if the file is not a decodable image, which catches truncated downloads and error pages saved as images. Checking the byte size also tells you whether the response is plausible. At this point the loop is complete: submit, poll, download, verify.

Troubleshooting common failures

Most first integrations fail in a handful of predictable ways. Diagnose them in this order.

Authentication errors (401/403). The key is missing, malformed, or lacks permission. Confirm the environment variable is exported in the shell or process that runs the request, and that the Authorization header uses the exact prefix the API expects.

Invalid or oversized reference images (400/422). The image URL may be unreachable from the API's side, the file may be corrupt, or the dimensions and file size may exceed the limit. Test with a small, known-good PNG first. If you pass a URL, make sure it is publicly fetchable; if you pass inline data, confirm the encoding.

Polling timeouts. If tasks consistently hit your timeout, increase the cap, lengthen the interval, or check whether the task actually failed with a reason you were not surfacing. Log the last task object before raising so you can see the state.

Rate limits (429). Back off and retry with jitter rather than retrying immediately. If you fan out many submissions, cap concurrency so bursts do not trigger limits, and make retries idempotent by tracking task ids.

For any error class, log the HTTP status, a truncated response body, and the task id — that trio is usually enough to tell whether the problem is auth, payload, or timing. Start with the curl example to isolate whether the issue is in your code or your inputs, then port the fix back to Python.

Related articles

View all articles →

Start building with persistent AI artifacts

Get your API key and call image, video and audio models through one task-based API.

Read the API docs