HiAPI
Blog

Tutorial

How to Call muse-image-1.0/image-to-image on the HiAPI API: curl, Python, and a Working Request

Oct 31, 2026 9 min read
奶油色底上的多层薄纸叠层插画,一块陶红色纸层作点缀,构图克制均衡,为教程文章封面图。

What an image-to-image request actually sends

Before you write a single line of code, it helps to know exactly what an image-to-image call expects. Unlike a plain text-to-image request where the prompt is the only creative input, muse-image-1.0/image-to-image needs a source image plus instructions for how to transform it.

At a minimum, your payload includes:

  • model: the identifier muse-image-1.0/image-to-image.
  • image: the source image you want to edit. This is typically sent as a base64-encoded string or as a publicly reachable URL, depending on what the endpoint accepts. Check the model's input schema for the exact field name and format.
  • prompt: a text description of the edit you want. Be specific about the change and what should stay the same.
  • Optional parameters such as strength or guidance (if the model exposes them) let you control how far the output may drift from the source. The exact set varies by model, so always confirm against the API reference rather than assuming.

The key mental shift is this: you are not generating from nothing. You are providing a starting point and a direction. A vague prompt like "make it better" will produce unpredictable results because the model has too much freedom. A prompt like "replace the sky with a sunset and keep the buildings unchanged" gives the model a clear constraint.

Also confirm the image constraints up front—supported formats, maximum dimensions, and file size. If your source image is a 12 MB PNG, it may need to be downscaled or converted to JPEG before the request will be accepted. Preparing the image correctly before you send it saves you a round trip and a confusing error message.

Once you have a source image and a clear edit prompt, you are ready to make your first call.

Your first request with curl

The fastest way to verify your integration is to send a request directly from the terminal. The exact endpoint path and payload schema are defined in the model's API reference; the example below shows the general shape so you can adapt it to the current specification.

curl -X POST https://api.hiapi.ai/v1/images/edits \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "muse-image-1.0/image-to-image",
    "image": "https://your-bucket.example.com/source.jpg",
    "prompt": "replace the sky with a warm sunset, keep the buildings unchanged",
    "strength": 0.6
  }'

Replace $HIAPI_API_KEY with an environment variable rather than pasting the key inline. If your tooling supports it, export the key in your shell session so it never lands in your command history.

The response you get back will include an identifier for the job. Because image editing is compute-intensive, the API typically does not return the finished image in the same response. Instead, you receive a job ID and a status. Save that ID—you will use it to poll for the result in the next step.

If the call fails, read the error body carefully. A 400 usually means a malformed payload or an unsupported image format. A 401 means the key is missing or wrong. A 429 means you are sending requests too quickly. Fixing these at the terminal stage is much easier than debugging them inside a full application.

The same request in Python

Once the curl call works, translate it into a small Python function. Using requests keeps the example dependency-light and portable.

import os
import requests

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

def submit_edit(image_url: str, prompt: str, strength: float = 0.6) -> str:
    payload = {
        "model": "muse-image-1.0/image-to-image",
        "image": image_url,
        "prompt": prompt,
        "strength": strength,
    }
    headers = {
        "Authorization": f"Bearer {HIAPI_API_KEY}",
        "Content-Type": "application/json",
    }
    response = requests.post(BASE_URL, json=payload, headers=headers, timeout=30)
    response.raise_for_status()
    data = response.json()
    return data["id"]

A few notes on this function:

  • The timeout argument prevents your script from hanging indefinitely if the network stalls. Thirty seconds is generous for a submission call, but adjust as needed.
  • raise_for_status() turns HTTP errors into exceptions so you can catch them in one place.
  • Returning only the job ID keeps the function focused. The polling logic belongs in a separate function.

If you prefer sending the source image as bytes rather than a URL, read the file, base64-encode it, and place the result in the image field. Confirm the expected format in the documentation before switching, since the two encodings are not always interchangeable.

This function is intentionally minimal. In a real service you would add logging, retry handling, and type validation, but the core request pattern is exactly what you see here.

Handling async jobs and polling for results

Image editing takes time—often seconds to tens of seconds depending on the prompt and resolution. The API handles this by accepting your request, returning a job ID, and processing the work in the background. Your job is to check back until the status reaches a terminal state.

Polling sounds simple, but a naive loop that fires every 100 milliseconds will waste quota and may trigger rate limits. Use a sane interval and a retry limit:

import time
import requests

def poll_job(job_id: str, interval: float = 2.0, max_attempts: int = 30) -> dict:
    url = f"https://api.hiapi.ai/v1/jobs/{job_id}"
    headers = {"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"}
    for attempt in range(max_attempts):
        response = requests.get(url, headers=headers, timeout=15)
        response.raise_for_status()
        data = response.json()
        status = data["status"]
        if status == "succeeded":
            return data
        if status == "failed":
            raise RuntimeError(data.get("error", "job failed"))
        time.sleep(interval)
    raise TimeoutError(f"job {job_id} did not finish after {max_attempts} attempts")

A two-second interval is a reasonable starting point. If your jobs usually finish in under five seconds, you can poll more frequently at the start and back off over time. If they routinely take longer, reduce the polling frequency to avoid unnecessary calls.

The retry limit is your safety net. Without it, a stuck job could loop forever. When the limit is hit, raise a clear error so the caller knows the operation timed out rather than silently hanging.

In production, consider moving polling to a background worker or queue so your request-handling thread is not blocked waiting for an image to finish rendering.

Downloading and storing the output image

When the job reaches the succeeded state, the response contains one or more output images, typically as URLs. Your next step is to download the image and store it durably.

def download_image(result: dict, dest_path: str) -> None:
    image_url = result["output"][0]["url"]
    response = requests.get(image_url, timeout=30)
    response.raise_for_status()
    with open(dest_path, "wb") as f:
        f.write(response.content)

A few practical considerations:

  • Output URLs may be temporary. Treat them as short-lived and download promptly rather than storing the URL as your only reference.
  • Write the file in binary mode. Image responses are not text; opening with "w" will corrupt the data.
  • Verify the file is non-empty before declaring success. A zero-byte download usually means the URL expired or the request was blocked.

For anything beyond a local script, stream the download directly to your storage provider instead of saving to disk first. Most object storage SDKs accept a file-like object or a byte stream, so you can pipe response.raw or response.content into your upload call without an intermediate file.

Finally, record the job ID alongside the stored object. If the output is later corrupted or you need to reproduce a specific edit, the job ID plus your original inputs is what lets you trace back to what was sent.

Common failure points and how to fix them

Most integration problems fall into a handful of categories. Here is how to diagnose each.

The request is rejected before a job is created

A 400 error at submission usually means the payload is wrong. Check that model is exactly muse-image-1.0/image-to-image, that image is reachable or correctly base64-encoded, and that prompt is a non-empty string. If the image is a URL, confirm it is publicly accessible—private buckets or signed URLs that expired will fail validation.

The job fails during processing

If polling returns a failed status, read the error message in the response body. Common causes include an image that is too large, a prompt that violates content policy, or a parameter value outside the allowed range. Reduce the image size and simplify the prompt to isolate the cause.

Polling times out

If you hit your retry limit without a terminal status, the job may legitimately need more time, or it may be stuck. First, increase max_attempts and confirm with the API reference what typical processing time looks like. If jobs consistently time out, check whether your source images are unusually large. Do not simply remove the retry limit—bounded retries prevent silent infinite loops.

Rate limiting

A 429 response means you are sending requests too fast. Back off, respect any retry-after header, and stagger concurrent submissions. If you are processing a batch of images, process them sequentially or with a small concurrency limit rather than firing all at once.

Output is missing or truncated

If the job succeeds but the downloaded file is empty, the output URL may have expired between polling and download. Download immediately after success. If the image is corrupted, confirm you are writing in binary mode and not applying any text encoding to the stream.

Debugging is easier when you log the request payload, the job ID, and the status transitions. With those three pieces of information, almost any failure can be traced back to its source.

With the request shape, the curl and Python patterns, polling logic, and download step in place, you have a working pipeline for muse-image-1.0/image-to-image on the HiAPI API. Start with the terminal example, move it into a script, and add retries and logging once the happy path is solid.

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