A working image-to-image recipe for 2K/4K generative upscaling, with Python, callbacks, and error handling.
Choose a model, enter your prompt, and see the result.
HiAPI Blog
HiAPI
Generate it with HiAPI
If you have a low-resolution product photo, a small AI-generated thumbnail, or an old scan and you need a crisp 2K/4K version, you don't need a dedicated super-resolution service. hiapi's unified /v1/tasks endpoint lets you run the same request against several image models that accept an existing image and redraw it at a higher resolution tier. This recipe shows the full working flow: submit the job, poll (or get a callback) for the result, and download the upscaled file.
One honesty note up front: hiapi does not (yet) expose a dedicated pixel-preserving super-resolution model. What you get here is generative upscaling — the model repaints your source image at a higher target resolution while following a prompt that tells it to preserve composition and add detail. For photos and illustrations this is usually exactly what people mean by "increase image resolution," but it isn't lossless interpolation, so don't reach for it if you need bit-exact pixel scaling of, say, a screenshot with small text.
You'll build a small script that:
Before you start:
Authorization: Bearer sk-<your-key> — there's no way to call these endpoints without one.localhost paths or private buckets won't work — upload to any public host first.requests installed (pip install requests), or just curl if you'd rather test from the shell.The cleanest model for this is seedream-5.0-lite/image-to-image. Its input schema requires exactly four fields — prompt, image_urls, aspect_ratio, and resolution — and resolution only accepts 2K or 4K (there's no 1K tier on this route, which conveniently means you can't accidentally ask it to shrink your image).
import time
import requests
API_KEY = "sk-your-hiapi-key" # from https://www.hiapi.ai/en/dashboard/api-keys
BASE = "https://api.hiapi.ai/v1/tasks"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}
def create_upscale_task(source_image_url: str) -> str:
payload = {
"model": "seedream-5.0-lite/image-to-image",
"input": {
"prompt": "Same image, preserved composition and colors, sharp fine detail, no artifacts",
"image_urls": [source_image_url],
"aspect_ratio": "1:1", # match your source image's ratio
"resolution": "4K",
},
}
resp = requests.post(BASE, headers=HEADERS, json=payload, timeout=60)
resp.raise_for_status()
body = resp.json()
task_id = body["data"]["taskId"]
return task_id
def wait_for_result(task_id: str, timeout_s: int = 300, poll_every: int = 5) -> str:
deadline = time.time() + timeout_s
while time.time() < deadline:
resp = requests.get(f"{BASE}/{task_id}", headers=HEADERS, timeout=30)
resp.raise_for_status()
task = resp.json()["data"]
if task["status"] == "success":
return task["output"][0]["url"]
if task["status"] == "fail":
err = task.get("error", {})
raise RuntimeError(f"task failed: {err.get('code')} {err.get('message')}")
time.sleep(poll_every)
raise TimeoutError(f"task {task_id} did not finish in {timeout_s}s")
if __name__ == "__main__":
task_id = create_upscale_task("https://your-cdn.example.com/product-photo-small.jpg")
result_url = wait_for_result(task_id)
print("4K result:", result_url)
Equivalent create-task call with curl, if you just want to see the request shape:
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer sk-your-hiapi-key" \
-H "Content-Type: application/json" \
-d '{
"model": "seedream-5.0-lite/image-to-image",
"input": {
"prompt": "Same image, preserved composition and colors, sharp fine detail, no artifacts",
"image_urls": ["https://your-cdn.example.com/product-photo-small.jpg"],
"aspect_ratio": "1:1",
"resolution": "4K"
}
}'
A few things worth calling out about the request:
image_urls is an array, even though this model only reads the first element. Passing a bare string instead of a list is a common source of 400s.wait_for_result() returns, download the bytes and store them yourself — don't keep the hiapi-hosted link as your permanent asset URL.aspect_ratio to match it (4:3, 16:9, 9:16, 3:2, 2:3, or 21:9) so the model doesn't crop or pad your subject.Prefer callbacks over polling at scale. If you're upscaling more than a handful of images, don't hammer GET /v1/tasks/<id> in a loop for every job — pass a callback object and let hiapi push the result to you instead:
payload = {
"model": "seedream-5.0-lite/image-to-image",
"input": {...},
"callback": {
"url": "https://your-service.example.com/hooks/hiapi-task",
"when": "final",
},
}
Your webhook receives the same task object you'd get from polling (status, output, error) — verify its shape defensively, since a network retry could deliver it more than once.
Give every job an idempotency-friendly key on your side. The task API doesn't take a client-supplied idempotency key today, so if your job runner might retry a failed HTTP call, check whether you already have a stored taskId for that source image before submitting a duplicate task — otherwise a transient timeout on your end can turn into two billable upscales for the same file.
Handle the two distinct error shapes. A malformed request body comes back as HTTP 400 with a flat {"code": 400, "error_code": "INVALID_REQUEST", "message": "..."}. A missing or wrong API key comes back as HTTP 401 with a nested {"error": {"code": "permission_denied", "message": "..."}}. Check the status code first, then branch:
if resp.status_code == 401:
raise RuntimeError(f"auth error: {resp.json()['error']['message']}")
if resp.status_code == 400:
raise RuntimeError(f"bad request: {resp.json()['message']}")
resp.raise_for_status()
Pick the model by what input you have. All three routes below live on the same /v1/tasks endpoint — only the model string and input fields change:
| You have | Model | Required input fields |
|---|---|---|
| An existing image, want a clean 2K/4K redraw | seedream-5.0-lite/image-to-image | prompt, image_urls, aspect_ratio, resolution (2K|4K) |
| An existing image, want quality-vs-cost control | gpt-image-2/image-to-image@ext | prompt, image_urls, quality (low|medium|high), resolution (1K|2K|4K) |
| No source image — generating a fresh high-res asset from a text description | wan2.7-image/text-to-image | prompt (plus optional resolution and aspect_ratio) |
Note the @ext suffix on the second row — that's a route variant on the base gpt-image-2 model (hiapi exposes some models under more than one route with slightly different pricing/quality tiers), not a separate model family. Exact per-request cost for each of these depends on the resolution and quality tier you pick, so check current numbers on the pricing page rather than hardcoding a number in your budget logic.
Validate before you submit. All three models reject unknown input fields outright (additional properties 'x' not allowed) rather than silently ignoring them, and enum fields like resolution and aspect_ratio are case- and value-sensitive. If you're building a wrapper, validate against the exact enum values above client-side so a typo fails fast instead of burning a request.
Does hiapi have a dedicated image upscaler or super-resolution model? Not a dedicated pixel-preserving one. The workflow above uses general-purpose image-to-image models to regenerate your image at a higher resolution tier, which works well for photos and illustrations but isn't lossless upscaling.
Can I increase resolution without changing the image content? You can get very close by prompting the model to preserve composition, colors, and subject exactly ("same image, do not alter composition or add/remove elements, only increase detail and sharpness"), but because this is a generative redraw rather than interpolation, expect small differences in fine texture versus a true lossless upscaler.
What's the maximum resolution I can generate?
All three models above cap out at a 4K resolution tier. If you need larger dimensions than that, you'll need to tile the source image and upscale sections separately, or use a client-side resizing step after the API call.
Do I need an API key to try this?
Yes — every /v1/tasks call requires Authorization: Bearer sk-<your-key>. There's no key-less sandbox for task creation; grab a key from your dashboard first.
Can I batch-upscale a folder of images?
Yes — loop over your image URLs, submit one task per image, and either poll each taskId or (better, at volume) register a single callback URL and match results back to your jobs using the taskId you stored when you created each one.
Why did my request fail with "additional properties not allowed"?
You sent a field the model's schema doesn't recognize — for example size instead of resolution + aspect_ratio, or a stray parameter copied from a different model's example. Check the required-fields table above for the exact field names each model accepts.