
qwen-image-3.0 is Alibaba's latest text-to-image model, live on hiapi's async task API today. This guide gives you a working request in curl and Python, the exact input schema, and the production patterns (callbacks, polling, error handling) you need before shipping it.
A script that submits a prompt to qwen-image-3.0, polls until the image is ready, and downloads the result. Same flow works for qwen-image-3.0-pro — swap the model id.
Prerequisite: an hiapi API key. Grab one from the dashboard — you can't run any of this without one.
hiapi exposes one task interface for every generation model: POST /v1/tasks to create the job, then GET /v1/tasks/{id} to check status and pull the result. qwen-image-3.0 accepts a prompt (required) and an optional size.
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer sk-your-api-key" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen-image-3.0",
"input": {
"prompt": "a ceramic mug on a wooden table, soft morning light, product photography",
"size": "1024*1024"
}
}'
This returns a task id immediately:
{ "code": 200, "data": { "taskId": "tk-hiapi-01..." }, "message": "success" }
Poll for the result:
curl -s https://api.hiapi.ai/v1/tasks/tk-hiapi-01... \
-H "Authorization: Bearer sk-your-api-key"
Once status flips to success, the image URL is at data.output[0].url:
{
"data": {
"status": "success",
"output": [
{ "type": "image", "url": "https://temp.hiapi.ai/.../result-0.png", "expireAt": 1787019376 }
]
}
}
expireAt is a unix timestamp — output URLs are temporary. Download the bytes (or copy them to your own storage) as soon as the task completes; don't treat the URL as a permanent hotlink.
import time
import requests
API_KEY = "sk-your-api-key"
BASE = "https://api.hiapi.ai/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
def generate(prompt: str, size: str = "1024*1024") -> str:
resp = requests.post(
f"{BASE}/tasks",
headers=HEADERS,
json={"model": "qwen-image-3.0", "input": {"prompt": prompt, "size": size}},
timeout=30,
)
resp.raise_for_status()
task_id = resp.json()["data"]["taskId"]
while True:
status = requests.get(f"{BASE}/tasks/{task_id}", headers=HEADERS, timeout=30).json()["data"]
if status["status"] == "success":
return status["output"][0]["url"]
if status["status"] == "fail":
raise RuntimeError(status.get("error", {}).get("message", "generation failed"))
time.sleep(2)
if __name__ == "__main__":
print(generate("a ceramic mug on a wooden table, soft morning light"))
qwen-image-3.0 takes a strict JSON schema — unknown fields get rejected with a 400, so don't guess extra parameters:
| field | type | required | notes |
|---|---|---|---|
prompt | string | yes | the only required field |
size | string | no | "WIDTH*HEIGHT" — use * as the separator, not x. Omit it to get the 1024×1024 default. |
Sending size with an x separator (e.g. "1024x1024") is accepted at submission time but the task fails during generation — always use the * form. qwen-image-3.0-pro shares the same two-field schema.
Use callbacks instead of polling in production. Add a callback object to the task body and hiapi POSTs the final result to your endpoint instead of you hammering GET /tasks/{id} in a loop:
{
"model": "qwen-image-3.0",
"input": { "prompt": "..." },
"callback": { "url": "https://your-app.com/webhooks/hiapi", "when": "final" }
}
when: "final" is the only supported value today — you get exactly one callback per task, fired on success or failure. See the create task reference and get task detail reference for the full request/response shapes.
Idempotency. The task API doesn't take a client-supplied idempotency key, so retries on your side create new tasks (and new charges). If you need exactly-once semantics, track submitted prompts/task ids in your own datastore before retrying a timed-out request.
Polling vs. callbacks. Polling is simpler to get running locally and fine for scripts or batch jobs; callbacks are the right call for anything user-facing or running in production, since they avoid both wasted requests and the tail latency of a fixed poll interval.
Error handling. A bad or missing API key returns HTTP 401 with error_code: "permission_denied" — check for that explicitly before assuming a network issue. Malformed input returns HTTP 400 with error_code: "INVALID_REQUEST" and a message naming the offending field, which is what the schema table above was built from. See authentication for header details.
POST /v1/tasks spec including callbacksWhat's the difference between qwen-image-3.0 and qwen-image-3.0-pro? Same request schema (prompt + size), different underlying model tier — pro is the higher-fidelity option. Swap the model field to switch between them; no other code changes needed.
Why did my request return 200 but the task later failed? Task creation only validates the request shape, not every value. Sending size with an x separator instead of * is a common example — it's accepted at submission and fails during generation. Check the error object on the task detail response for the reason.
Can I use size values other than square? Yes, pass any "WIDTH*HEIGHT" string. Check the model page for the current list of supported dimensions and pricing per size, since larger outputs cost more.
Do I need a webhook to use this API? No — polling GET /v1/tasks/{id} works fine for scripts, cron jobs, and low-volume use. Callbacks are an optimization for production traffic, not a requirement.
Is there a free tier? hiapi doesn't offer unauthenticated or free generation — every request needs a valid API key. Check pricing for current per-image cost.
Key Takeaways