wan2.7-image-standard is the standard tier of Tongyi Wanxiang's Wan 2.7 Image model. The thing that makes it worth a dedicated page: one model id does both text-to-image and reference-based image editing, it caps at 2K, and it bills a flat $0.0345 per image — cheaper than the 4K-capable Pro tier. This guide gives you a request that runs, the exact input schema (verified against the live API), the reference-editing path, and the boundary behavior you need to handle in production.
Everything in the schema and error sections below was confirmed by probing the live /v1/tasks endpoint while writing — the enum lists, the accepted field set, and the failure shape are quoted from real API responses, not from memory.
What you'll build and what you need
You'll submit a prompt (optionally with reference images), poll until the task finishes, and download the image. You need one thing first: an API key from the hiapi quickstart. Export it as HIAPI_API_KEY. There is no no-key path — every request is authenticated with Authorization: Bearer sk-<your-key>.
wan2.7-image-standard runs on hiapi's async task API: you POST /v1/tasks to create a task, then poll the detail endpoint until it reports a terminal status.
Text-to-image: the minimal request
Create the task:
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "wan2.7-image-standard",
"input": {
"prompt": "A quiet Shanghai lane at blue hour after rain, a small ceramic studio with warm paper lanterns, glazed teapots in the window, wet stone reflections, a bicycle leaning against the wall, cinematic 35mm photograph, high detail",
"aspect_ratio": "16:9",
"resolution": "2K"
}
}'
The response carries the task id in data.taskId:
{ "code": 200, "data": { "taskId": "tk-hiapi-01M5D5..." }, "message": "success" }
Then poll GET /v1/tasks/<taskId> until data.status is success, and read the image URL from data.output[0].url:
import os, time, requests
API = "https://api.hiapi.ai/v1/tasks"
HEADERS = {"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}"}
def generate(prompt, aspect_ratio="16:9", resolution="2K", image_urls=None, timeout=600):
payload = {"model": "wan2.7-image-standard",
"input": {"prompt": prompt,
"aspect_ratio": aspect_ratio,
"resolution": resolution}}
if image_urls: # reference editing (see next section)
payload["input"]["image_urls"] = image_urls
create = requests.post(API, headers=HEADERS, json=payload).json()
if create.get("code") != 200: # synchronous validation error (e.g. bad enum)
raise RuntimeError(create)
task_id = create["data"]["taskId"]
deadline = time.time() + timeout
while time.time() < deadline:
data = requests.get(f"{API}/{task_id}", headers=HEADERS).json()["data"]
if data["status"] == "success":
return requests.get(data["output"][0]["url"]).content
if data["status"] == "fail": # terminal failure — do NOT keep polling
raise RuntimeError(data.get("error"))
time.sleep(5)
raise TimeoutError(task_id)
png = generate("...") # save the bytes immediately — see the expiry note below
Both aspect_ratio and resolution are optional (the model has defaults), but set them explicitly — resolution only accepts 1K or 2K, and both bill the same, so there is no cost reason to leave it at the default.
Reference-based editing: the same model id, plus image_urls
This is what separates the standard tier from the Pro tier. Pass reference images in image_urls (an array, up to nine references per the model spec) and the model edits or recomposes based on them while still following your text prompt:
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "wan2.7-image-standard",
"input": {
"prompt": "Place this product on a sunlit marble kitchen counter beside fresh herbs, soft morning light, lifestyle product photography",
"image_urls": ["https://your-storage.example.com/product.jpg"],
"resolution": "2K"
}
}'
In Python it's the image_urls=[...] argument to the helper above — nothing else changes. prompt stays required even when you pass references (the API rejects a request with no prompt: missing required field "prompt").
The field name matters: it is image_urls. The plausible alternatives — reference_image_urls, image_input, input_urls — are all rejected by the schema with additional properties '<name>' not allowed. If you're porting code from another model's editing endpoint, this is the first thing to fix.
The exact input schema
The schema is strict: any field not in this list returns 400 INVALID_REQUEST with additional properties '<name>' not allowed. These are all the fields it accepts:
| Field | Type | Required | Notes |
|---|---|---|---|
prompt | string | yes | Required for both t2i and editing. English or Chinese. |
aspect_ratio | string enum | no | One of 1:1, 16:9, 4:3, 21:9, 3:4, 9:16, 8:1, 1:8. Anything else (e.g. 3:2) → 400. |
resolution | string enum | no | 1K or 2K only. No 4K on this tier. |
image_urls | string[] | no | Reference images for editing (up to 9). Presence switches it into reference mode. |
seed | integer | no | For reproducible outputs. |
Fields you might reach for that this model does not accept: negative_prompt, n, size, and prompt_optimizer are all rejected as unknown properties. There is no batch (n) parameter — submit one task per image.
Production notes and tested behavior
A 200 at submit does not mean the image is coming. wan2.7-image-standard validates enums synchronously (a bad resolution or aspect_ratio fails the POST immediately with 400 INVALID_REQUEST), but it does not validate reference URLs at submit time. A malformed or unreachable entry in image_urls is accepted with 200 and a taskId, then the task fails about a second later. I verified this directly: a task created with an invalid reference URL came back terminal as
{ "status": "fail", "error": { "code": "INVALID_REQUEST", "message": "invalid request parameters for this model" } }
The practical consequence: always poll for the terminal state and branch on data.status == "fail" (reading data.error). A failed task is reported as HTTP 200 with data.status: "fail" — the failure lives inside data.error, not in a top-level HTTP error. Make sure your reference URLs are publicly reachable before you submit.
Download the output immediately. data.output[0].url is a temporary link with an expireAt timestamp, not a permanent CDN asset. Fetch the bytes and move them to your own storage the moment the task succeeds; stale URLs 404. If you've hit this, see why your hiapi output URL 404s and how to handle it.
Polling vs. callback. The example above polls every five seconds, which is fine for scripts and small batches. For production throughput, attach a callback to the create request so hiapi notifies you on the terminal event instead of you holding the connection — the mechanics are in the create-task docs.
Standard vs. Pro: which tier to call
hiapi hosts two Wan 2.7 Image tiers, and they are different model ids, not a parameter:
wan2.7-image-standard(this guide) — text-to-image and reference editing,1K/2K, flat $0.0345 per image at either resolution.wan2.7-image/text-to-image— the Pro tier: text-to-image only, up to 4K, flat $0.08 per image. See the Wan 2.7 Image Pro page and the walkthrough on using it for 4K e-commerce product photos.
Pick the standard tier when you need reference-based editing, or when 2K is enough and you want to pay less than half the Pro per-image price. Reach for Pro only when you specifically need native 4K output — that's the one thing the standard tier can't do. Current pricing for both is on the pricing page.
FAQ
Does wan2.7-image-standard support image editing, or only text-to-image? Both, from the same model id. Add an image_urls array to switch into reference-based editing; omit it for plain text-to-image.
What's the maximum resolution? 2K. The resolution field only accepts 1K and 2K; the live API rejects anything else. If you need 4K, use the Pro tier (wan2.7-image/text-to-image).
Why did my task return 200 and then fail? Reference URLs aren't checked when you submit — only on execution. An unreachable or malformed image_urls entry creates the task (HTTP 200) and then fails terminally with INVALID_REQUEST. Poll for data.status and verify your URLs are public.
Can I set a negative prompt? No. negative_prompt is not an accepted field on this model (nor are n, size, or prompt_optimizer). Steer with the positive prompt and seed.





