
slug: flux-2-klein-4b-text-to-image-api-guide locale: en title: How to Use flux-2-klein-4b/text-to-image via the hiapi API category: tutorial summary:
A script that sends a prompt to flux-2-klein-4b/text-to-image, waits for the image, and downloads it — plus the production details (callbacks, idempotency, error shapes) you need before this runs unattended.
Prerequisite: grab an API key from the hiapi dashboard. Every request below authenticates with Authorization: Bearer sk-<your-api-key>.
hiapi generation models run behind one unified async endpoint: POST /v1/tasks creates a job and hands back a taskId immediately; you then poll GET /v1/tasks/{taskId} until it reaches a terminal state.
curl -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer sk-<your-api-key>" \
-H "Content-Type: application/json" \
-d '{
"model": "flux-2-klein-4b/text-to-image",
"input": {
"prompt": "a red fox walking through fresh snow, golden hour light",
"aspect_ratio": "16:9"
}
}'
That returns:
{"code": 200, "data": {"taskId": "tk-hiapi-01K..."}, "message": "success"}
Poll the same task until data.status flips from handling to success (it briefly passes through archiving in between):
curl https://api.hiapi.ai/v1/tasks/tk-hiapi-01K... \
-H "Authorization: Bearer sk-<your-api-key>"
{
"code": 200,
"data": {
"status": "success",
"output": [
{"type": "image", "url": "https://temp.hiapi.ai/.../01K...-0.jpg", "expireAt": 1785939579}
]
},
"message": "success"
}
data.output[0].url is the finished image. It's a temporary link — in testing it expired about 7 days after generation — so download the bytes and store them yourself rather than linking to it directly.
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"}
resp = requests.post(f"{BASE}/tasks", headers=headers, json={
"model": "flux-2-klein-4b/text-to-image",
"input": {
"prompt": "a red fox walking through fresh snow, golden hour light",
"aspect_ratio": "16:9",
},
})
resp.raise_for_status()
task_id = resp.json()["data"]["taskId"]
while True:
task = requests.get(f"{BASE}/tasks/{task_id}", headers=headers).json()["data"]
if task["status"] == "success":
image_url = task["output"][0]["url"]
break
if task["status"] == "failed":
raise RuntimeError(f"generation failed: {task}")
time.sleep(3)
image_bytes = requests.get(image_url).content
open("fox.jpg", "wb").write(image_bytes)
flux-2-klein-4b/text-to-image has a strict input schema — sending a field it doesn't recognize (size, n, negative_prompt, whatever another hiapi image model accepts) gets rejected outright, so don't copy params across models without checking the model page first.
| Field | Required | Notes |
|---|---|---|
prompt | Yes | String describing the image. |
aspect_ratio | No | One of 1:1, 16:9, 9:16, 3:2, 2:3, 4:3, 3:4, 5:4, 4:5, 21:9, 9:21. Defaults to 1:1 if omitted. |
seed | No | Integer. Reuse a seed to make results reproducible across calls. |
Full parameter reference lives on the model page; current per-image pricing is on the pricing page — check there rather than hardcoding a number, since usage-based rates change.
Callbacks over polling. For anything beyond a quick script, skip the poll loop and add callback.url (plus "when": "final") to the task-creation body — hiapi POSTs the result to your endpoint once the task finishes, exactly the same payload shape you'd get from GET /v1/tasks/{taskId}. See Create Task for the callback field and signature-verification details.
{
"model": "flux-2-klein-4b/text-to-image",
"callback": {"url": "https://yourapp.com/hooks/hiapi", "when": "final"},
"input": {"prompt": "a red fox walking through fresh snow, golden hour light"}
}
Idempotency. Set an Idempotency-Key header (up to 255 bytes) on your POST /v1/tasks call. Retrying the same request with the same key under the same account won't create a second task — you get the original taskId back. That's what makes it safe to retry a request that timed out on your side without double-billing.
Error handling. Two different failure shapes show up in practice, so check both:
input (missing prompt, an out-of-range aspect_ratio, an unsupported field) returns HTTP 400 with {"error_code": "INVALID_REQUEST", "message": "..."}.{"error": {"code": "permission_denied", "message": "...", "request_id": "..."}}.Branch on the HTTP status first, then read whichever error field is present — don't assume one envelope shape for every failure.
Does flux-2-klein-4b/text-to-image support image-to-image?
Not directly — that's a separate model, flux-2-klein-4b/image-to-image, with its own input schema.
What's the difference between flux-2-klein-4b and flux-2-klein-9b?
Both are text-to-image endpoints in the same FLUX.2 [klein] family; 9b is the larger of the two. Check the 9b model page for its current pricing and parameters before switching.
Can I request multiple images in one call?
No — the schema doesn't accept an n or count field. Send one request per image you need.
How long is the returned image URL valid?
The output[0].url is a temporary link with its own expireAt timestamp; treat it as short-lived (roughly a week in testing) and download the bytes right after the task succeeds instead of storing the URL.
Why am I getting a 401 permission_denied error?
Your key is missing, invalid, or doesn't have access to this model. Confirm you're sending Authorization: Bearer sk-... with a key from your dashboard, not a placeholder.
Do I need to run my own server to use this API?
Only if you want callbacks. Polling GET /v1/tasks/{taskId} from a script, cron job, or serverless function works fine for low-volume use; switch to callback.url once you're generating at scale.
Key Takeaways