
Every sales channel wants your product images at a different size: Amazon wants big squares on white, Instagram wants 4:5 portraits, Pinterest wants 2:3 pins, and your own storefront hero wants something ultra-wide. This guide shows how to generate all of them programmatically with the hiapi API — using gpt-image-2 and wan2.7-image — and how to land on exact pixel dimensions every time.
Prerequisites: a hiapi API key (create one in the dashboard), plus curl or Python. Total cost depends on the model and resolution you pick — see pricing.
First, the thing that trips everyone up: image models on hiapi do not accept pixel sizes. There is no size, width, or height field. If you try, the API tells you explicitly:
{
"code": 400,
"error_code": "INVALID_REQUEST",
"message": "invalid input: <root>: additional properties 'size' not allowed"
}
Instead, dimensions are controlled by two fields in input:
aspect_ratio — the shape of the frame, from a fixed per-model list.resolution — the output class: "1K", "2K", or "4K" (roughly 1024 / 2048 / 4096 px on the long edge; exact pixel counts vary by ratio, so always check the file you get back).Here is what the two models in this guide actually accept (verified against the live API):
gpt-image-2 | wan2.7-image/text-to-image | |
|---|---|---|
prompt | required | required |
aspect_ratio | auto, 1:1, 3:2, 2:3, 4:3, 3:4, 5:4, 4:5, 16:9, 9:16, 2:1, 1:2, 3:1, 1:3, 21:9, 9:21 — 16 values | 1:1, 16:9, 4:3, 21:9, 3:4, 9:16, 8:1, 1:8 — 8 values |
resolution | 1K, 2K, 4K | 1K, 2K, 4K |
| Extras | image-to-image variant (gpt-image-2/image-to-image) | seed (integer) for reproducibility |
Pixel size / n | not accepted | not accepted |
The practical difference: gpt-image-2 has the ratio coverage for social and marketplace formats (4:5, 2:3, 5:4 are gpt-image-2-only), while wan2.7-image adds 8:1/1:8 ultra-wide strips that no other ratio list covers — useful for skinny promo banners — and is a popular choice for 4K studio shots.
So the recipe for exact dimensions is a two-step:
Commonly published requirements, mapped to API parameters:
| Channel | Typical target | aspect_ratio | resolution | Model note |
|---|---|---|---|---|
| Amazon main listing | ≥1000 px longest side (1600 px+ enables zoom), square, pure white background | 1:1 | 2K | either |
| Shopify product page | 2048×2048 recommended | 1:1 | 2K | either |
| Instagram feed (portrait) | 1080×1350 | 4:5 | 2K | gpt-image-2 only |
| Stories / Reels / TikTok | 1080×1920 | 9:16 | 2K | either |
| Pinterest pin | 1000×1500 | 2:3 | 2K | gpt-image-2 only |
| Etsy listing | 2700×2025 recommended | 4:3 | 4K | either (needs 4K class) |
| Site hero banner | e.g. 2560 px wide | 21:9 | 4K | either |
| Skinny promo strip | ultra-wide | 8:1 | 2K/4K | wan2.7-image only |
(Marketplace specs change; treat the pixel targets as the commonly cited recommendations and confirm against each platform's current documentation.)
Everything runs on the unified async task endpoint: POST /v1/tasks to create, GET /v1/tasks/<id> to poll. Create an Amazon-style square at the 2K class:
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer sk-YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-image-2",
"input": {
"prompt": "Studio product photo of a stainless steel water bottle on a pure white background, centered, soft shadow, e-commerce catalog style",
"aspect_ratio": "1:1",
"resolution": "2K"
}
}'
The response carries the task id at data.taskId. Poll it:
curl -s https://api.hiapi.ai/v1/tasks/TASK_ID \
-H "Authorization: Bearer sk-YOUR_KEY"
Poll until data.status is "success" (or "fail", with details under data.error). The image URL is at data.output[0].url. That URL expires (the output object carries an expireAt), so download it immediately and re-host on your own storage — never hotlink it in a listing.
This batch script creates all tasks up front (they run in parallel on the platform), polls each to completion, then resizes to the exact target dimensions with Pillow. ImageOps.fit handles both the downscale and any hairline crop in one call.
import os
import time
from io import BytesIO
import requests
from PIL import Image, ImageOps
API = "https://api.hiapi.ai/v1"
HDRS = {
"Authorization": f"Bearer {os.environ['HIAPI_KEY']}",
"Content-Type": "application/json",
}
# channel -> generation params + exact pixel target
SPECS = {
"amazon-main": {"model": "gpt-image-2", "aspect_ratio": "1:1", "resolution": "2K", "px": (1600, 1600)},
"shopify": {"model": "gpt-image-2", "aspect_ratio": "1:1", "resolution": "2K", "px": (2048, 2048)},
"instagram-feed": {"model": "gpt-image-2", "aspect_ratio": "4:5", "resolution": "2K", "px": (1080, 1350)},
"story-reel": {"model": "gpt-image-2", "aspect_ratio": "9:16", "resolution": "2K", "px": (1080, 1920)},
"pinterest-pin": {"model": "gpt-image-2", "aspect_ratio": "2:3", "resolution": "2K", "px": (1000, 1500)},
"etsy-listing": {"model": "wan2.7-image/text-to-image", "aspect_ratio": "4:3", "resolution": "4K", "px": (2700, 2025)},
"hero-banner": {"model": "wan2.7-image/text-to-image", "aspect_ratio": "21:9", "resolution": "4K", "px": (2560, 1097)},
}
PROMPT = (
"Studio product photo of a stainless steel water bottle on a pure white "
"background, centered, soft shadow, high detail, e-commerce catalog style"
)
def create_task(model: str, prompt: str, aspect_ratio: str, resolution: str) -> str:
body = {
"model": model,
"input": {"prompt": prompt, "aspect_ratio": aspect_ratio, "resolution": resolution},
}
data = requests.post(f"{API}/tasks", json=body, headers=HDRS, timeout=60).json()
task_id = (data.get("data") or {}).get("taskId")
if not task_id:
raise RuntimeError(f"create failed: {data}")
return task_id
def wait_task(task_id: str, timeout_s: int = 600) -> str:
deadline = time.time() + timeout_s
while time.time() < deadline:
data = requests.get(f"{API}/tasks/{task_id}", headers=HDRS, timeout=30).json()
task = data.get("data") or {}
if task.get("status") == "success":
return task["output"][0]["url"]
if task.get("status") == "fail":
err = task.get("error") or {}
raise RuntimeError(f"task {task_id} failed: {err.get('code')} {err.get('message')}")
time.sleep(5)
raise TimeoutError(f"task {task_id} did not finish in {timeout_s}s")
def save_exact(url: str, px: tuple, path: str) -> None:
# Output URLs expire -- download immediately, then fit to exact pixels.
raw = requests.get(url, timeout=120).content
img = Image.open(BytesIO(raw))
ImageOps.fit(img, px, Image.LANCZOS).save(path)
if __name__ == "__main__":
# create everything first: tasks run concurrently on the platform
tasks = {
name: create_task(s["model"], PROMPT, s["aspect_ratio"], s["resolution"])
for name, s in SPECS.items()
}
for name, task_id in tasks.items():
url = wait_task(task_id)
save_exact(url, SPECS[name]["px"], f"{name}.png")
print(f"{name}: {SPECS[name]['px']} -> {name}.png")
Two details worth copying even if you rewrite the rest:
4K and downscales. Downscaling with LANCZOS is visually lossless for catalog use; upscaling is not.taskId as soon as you create a task. If your worker crashes, resume polling the stored id instead of re-creating the task — re-creating generates (and bills) a second image.Often you don't want seven independent generations — you want the same approved product shot recomposed for each channel. That's gpt-image-2/image-to-image: it requires input_urls (an array of publicly reachable image URLs) alongside prompt, and accepts the same 16 aspect_ratio values:
{
"model": "gpt-image-2/image-to-image",
"input": {
"prompt": "Extend the background naturally to fill the new frame; keep the product unchanged and centered",
"input_urls": ["https://your-cdn.com/hero-shot.png"],
"aspect_ratio": "9:16"
}
}
Same task lifecycle, same exact-pixel step at the end. There's a full walkthrough in How to Use gpt-image-2 Image-to-Image via the hiapi API.
Callbacks instead of polling. For servers, pass a top-level callback when creating the task and skip the poll loop entirely:
{
"model": "gpt-image-2",
"input": {"prompt": "...", "aspect_ratio": "1:1", "resolution": "2K"},
"callback": {"url": "https://yourapp.com/hooks/hiapi", "when": "final"}
}
You get one POST at the terminal state. Treat the callback as a signal, not a source of truth: re-fetch GET /v1/tasks/<id> before acting, and dedupe by taskId. If your callback never arrives, work through Why your hiapi task callback isn't firing.
Error handling. Auth failures return 401 permission_denied — check the Authorization: Bearer sk-... header first. Validation failures return 400 with error_code: "INVALID_REQUEST", and the message is unusually helpful: it lists the exact allowed values (that's how the ratio tables above were verified). Input schemas differ per model — wan2.7-image takes a seed, gpt-image-2 doesn't; some fast models reject resolution entirely — so check each model's page before hard-coding fields.
Cost control. Price scales with model and resolution class. Generate at 1K while iterating on prompts, and only switch to 2K/4K for finals — current per-image rates are on the pricing page.
Can I pass exact pixel dimensions like 1080×1350 in the request?
No. size, width, and height are rejected with a 400 (additional properties ... not allowed). Generate at the matching aspect_ratio and a resolution class at or above your target, then do a local downscale to exact pixels — the Pillow one-liner above.
Which settings for Amazon main images?
aspect_ratio: "1:1", resolution: "2K", then resize to your target (1600×1600 is a solid default — above the 1000 px zoom threshold). Amazon main images also require a pure white background, so put that in the prompt, and review outputs before listing.
My target ratio isn't in the list (e.g. 1.91:1 link-ad format). What now?
Generate at the nearest wider-or-equal ratio that fully covers your target — 2:1 or 16:9 for 1.91:1 — and center-crop. ImageOps.fit in the script does exactly this: it crops the overshoot and resizes in one call.
How do I get true 4K product images?
Set resolution: "4K" on either model. wan2.7-image is a common pick for 4K studio-style shots; see the Wan 2.7 e-commerce guide for prompt patterns.
Do the generated image URLs expire?
Yes — each output carries an expireAt. Download the file as soon as the task succeeds and re-host it on your own storage. Never paste a task output URL into a product listing.
Can I generate multiple variants in one request?
No — there's no n parameter on these models. Create one task per image; tasks run concurrently, so batching in a loop (as in the script above) is just as fast in practice. On wan2.7-image you can vary seed to get controlled variations of the same prompt.
Key Takeaways