Tutorial
How to Use flux-1.1-pro via the HiAPI API: curl, Python, and Working Requests
A hands-on integration guide: requests that actually run, async handling, and prompt recipes with real outputs
On this page
What flux-1.1-pro Gives You Before You Write Code
flux-1.1-pro is a text-to-image model you call as a hosted endpoint through HiAPI.ai. You send a prompt and generation parameters as JSON, and you get back generated images. There is nothing to install, no weights to host, and no GPU capacity to manage. Your integration surface is one HTTP endpoint plus the credentials that authenticate it.
For developers, that means three things shape your design before you write a line of code.
First, the request is JSON in, images out. Your prompt, aspect ratio, and a few control parameters travel in the request body. The response carries image data or a reference to it, depending on how the job is delivered. Treat the request body as the contract and keep it in one place so curl and Python never drift apart.
Second, image generation is a slow operation relative to a normal API call. A few seconds is normal; a complex prompt at high resolution can take longer. Any production caller must assume latency and plan for it rather than treating the call like a database read.
Third, output quality is dominated by the prompt, not by wiring. The integration is the easy part. The craft is in describing what you want precisely and in verifying a recipe against a real output before you ship it.
Keep those three facts in mind and the rest of this guide is mechanical: build a request, confirm it runs, port it, then harden and tune it. Throughout, everything routes through the HiAPI API — one credential, one base URL, one JSON shape to learn.
Your First Request in curl
Start with curl. It removes every layer of abstraction between you and the endpoint, so when something fails you know it is your payload, not your framework.
A minimal call has three parts: the endpoint, the auth header, and the JSON body.
curl -X POST "https://api.hiapi.ai/v1/images/generations" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "flux-1.1-pro",
"prompt": "a ceramic coffee mug on a weathered wooden table, soft morning window light, shallow depth of field, product photography",
"aspect_ratio": "1:1"
}'
Read the body field by field:
modelnames the model you want. Keep it explicit so a future default change never silently alters your output.promptis the natural-language description. This is where nearly all of your quality budget lives.aspect_ratiocontrols framing. Choose it deliberately rather than relying on a default you have not checked.
Put your key in an environment variable (HIAPI_API_KEY) instead of pasting it into the command. That habit carries straight into Python and into any CI step you add later.
When the call succeeds you get a JSON response describing the finished image. Inspect it and locate the image reference or data before you close the terminal. When it fails you get an error body with a status code and a message; read the message literally, because a malformed field is the most common first failure.
One warning that saves an hour: do not paste a multi-line prompt with unescaped quotes into a shell. Use a heredoc or a file for anything longer than a short sentence.
curl -X POST "https://api.hiapi.ai/v1/images/generations" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
--data @request.json
That request.json is the artifact you should commit. It is the single source of truth for the payload, and the next section reuses it verbatim.
Porting the Same Call to Python
The goal of the port is not to write new logic; it is to send the identical JSON body. Load the same request.json so the two paths cannot drift.
import json
import os
import requests
API_URL = "https://api.hiapi.ai/v1/images/generations"
with open("request.json") as f:
payload = json.load(f)
response = requests.post(
API_URL,
headers={
"Authorization": f"Bearer {os.environ['HIAPI_API_KEY']}",
"Content-Type": "application/json",
},
json=payload,
timeout=120,
)
if response.status_code != 200:
print(response.status_code)
print(response.text)
response.raise_for_status()
print(response.json())
Three details matter here.
Auth and headers are the same headers. Using json=payload makes requests serialize the body and set the content type for you; the explicit header is harmless and documents intent.
Set a timeout. Never let a generation request hang on an unbounded default. A generous explicit timeout is fine — the point is that it is explicit.
Inspect the error body before raising. raise_for_status() throws away the response body in your logs if you let it run first. Print the status and text, then raise. You will want that text when a field is rejected.
If you are standardizing on an async HTTP client, the shape is unchanged: same URL, same headers, same payload, await client.post(...) with the same timeout. The payload contract is the constant; the client is an implementation detail.
Keep the payload out of your Python source. When a prompt recipe works, it belongs in a file or a database row, not in a string literal buried in a function, so you can diff it and rerun it.
Handling Async Jobs Without Blocking Your App
Treat generation as a job, not a function call. The practical consequences are the same whether your delivery path is synchronous-looking or returns a job you poll: your application must not tie up a request thread waiting on an image for an unbounded time.
A workable pattern has four steps.
1. Submit from a worker, not a user request
Accept the user's generate action in your web tier, persist a job row, enqueue it, and return immediately. A background worker performs the actual API call. The user polls your endpoint, not the model.
2. Bound the wait
Give every attempt a timeout you chose deliberately. If the platform returns a job that you must poll, poll with a fixed interval, a maximum number of attempts, and a deadline computed from job start rather than attempt count. Prefer modest backoff over a tight loop.
3. Retry only the failures that deserve it
Retry transport errors, timeouts, and server-side failures with exponential backoff and jitter, capped at a small number of attempts. Do not retry validation errors: a rejected field or an empty prompt will fail identically every time, and retrying just multiplies the delay before the user sees the real message.
4. Make the job idempotent from your side
Record the exact payload and a job ID in your database. If your worker crashes after submission but before recording success, you can re-check the job rather than blindly resubmitting. When you must resubmit, add a short client-side dedupe key so a transient failure does not queue three identical generations.
import time
def poll_until_done(fetch_status, job_id, deadline_seconds=180):
start = time.monotonic()
delay = 2.0
while True:
status = fetch_status(job_id)
if status["state"] in ("succeeded", "failed"):
return status
if time.monotonic() - start > deadline_seconds:
raise TimeoutError(f"generation {job_id} exceeded deadline")
time.sleep(delay)
delay = min(delay * 1.5, 10.0)
That helper is deliberately generic: it takes a fetch function so the same loop works for any job-shaped endpoint you integrate later. The important properties are the deadline, the capped backoff, and the terminal-state check — not the specific transport.
Structuring Prompts That Reliably Render
Long prompts are not better prompts. Structure is. A repeatable skeleton gives you consistent results and a place to change one variable at a time when something is off.
Use four slots, in this order:
- Subject — what is in frame, concretely. "A ceramic coffee mug" beats "something for a café."
- Style — the visual register: photographic, illustration, watercolor, 3D render, product shot. Name the medium and, where useful, the light.
- Composition — framing and camera: aspect ratio, shot distance, angle, depth of field, where the subject sits in frame.
- Constraints — the specific things you do and do not want, kept short.
[subject] a single ceramic coffee mug, matte glaze, small handle
[style] product photography, soft diffused window light, muted palette
[composition] 1:1, eye-level, subject centered, shallow depth of field, plain background
[constraints] no text, no logos, no hands
Flatten that into one prompt when you send it; the brackets are for you, not the model. The value of writing it in slots is that when the output is wrong you know which slot to edit.
A few working rules:
- Front-load the subject. The first clause carries the most weight.
- Prefer concrete nouns and adjectives to abstract mood words. "Matte glaze" changes pixels; "premium feel" mostly does not.
- Put camera language in the composition slot, not the subject slot. Mixing them produces eager, contradictory instructions.
- Change one slot at a time when iterating, and keep the exact prompt text that produced an accepted output.
Aspect ratio is a composition decision, not an afterthought. Choose it to match where the image will live — square for a grid, taller for a hero banner — and write the rest of the prompt to fit that frame.
Using Negative Lists Effectively
A negative list tells the model what to avoid. It is a sharp tool with a narrow useful range: great for suppressing a recurring artifact, bad as a dumping ground for every imperfection you can name.
Keep negatives short and specific. Five to eight short items is plenty for most prompts. Name concrete things:
no text, no watermark, no logos, no extra fingers, no harsh shadows
That list works because each entry names something visible and checkable. Contrast it with vague negations like "not ugly" or "bad quality" — they carry little actionable signal and dilute the items that do matter.
Where negatives genuinely help:
- Suppressing artifacts you keep seeing. If hands or text keep appearing, naming them is the direct fix.
- Brand and legal hygiene. Excluding logos and watermarks before an image reaches a user-facing surface.
- Enforcing a clean background for product or UI-adjacent imagery.
Where they fight you:
- Contradicting the positive prompt. "No shadows" in the negatives against "dramatic side lighting" in the subject is a conflict the model resolves unpredictably. Fix the positive prompt instead.
- Negating things that were never likely. Long lists of improbable failures add noise without changing the render.
- Using negatives to compensate for a vague subject. If the subject is unclear, no negative list rescues it.
One practical detail: some clients accept negatives as a separate field rather than appended text. Check which your request supports and keep the field in your saved payload so curl and Python behave identically.
Prompt Recipes with Real Outputs
The recipes below are starting points, not guarantees. The honest workflow is: run the recipe through your own integration, look at the output, and adjust one slot. Treat any output you have not personally generated as unverified.
Recipe 1 — Clean product shot
A single matte-black wireless earbud case on a light concrete surface,
studio product photography, soft key light from the left, 1:1, centered,
shallow depth of field, plain background. No text, no logos, no hands.
Expected character: crisp, centered subject with a soft falloff and an uncluttered background. If the surface reads too busy, tighten "plain background" and drop any texture words. If the light is too flat, add a single directional keyword — nothing more.
Recipe 2 — Editorial portrait
Portrait of a person in a wool coat, natural window light, calm expression,
3:4, chest-up framing, subject slightly off-center, muted film palette.
No text, no watermark.
Expected character: soft directional light, restrained color. If framing drifts wider than intended, move "chest-up framing" earlier and remove competing composition words.
Recipe 3 — Isometric illustration
Isometric illustration of a small reading nook: armchair, floor lamp, bookshelf,
flat vector style, limited palette of warm neutrals and one accent color,
1:1, centered composition, clean white background. No text, no gradients.
Expected character: crisp geometry and flat fills. If the palette wanders, name the accent color explicitly, and keep "clean white background" as the final clause so it wins.
Recipe 4 — Hero banner
A wide misty mountain ridge at sunrise, layered depth, minimal detail,
16:9, subject occupying the lower third, generous empty sky for overlaid text.
No text, no watermark.
Expected character: a quiet, low-detail image with usable negative space. The "overlaid text" phrasing reserves room; it does not render letters, and the negative list keeps text out of the image itself.
For each recipe, log the exact payload, the aspect ratio, and the date. When you revisit it in a month, you can rerun the recorded payload and compare against the recorded output instead of guessing why the result changed.
Errors, Limits, and What to Check First
When a generation fails or the output is wrong, work down this checklist before touching your code.
Check the payload first
- Is the body valid JSON? A trailing comma or an unescaped quote is the most common first failure. Validate your saved
request.jsonbefore sending it. - Are field names exact? A misspelled parameter is usually rejected or silently ignored. Compare against the documented request shape rather than your memory.
- Is the model name correct and present? Do not rely on a default.
- Is the prompt non-empty and within the documented length? The platform documents limits; read them there rather than assuming a number.
Then check auth and transport
- Is the API key present and current? A 401 usually means the header is missing, malformed, or the key was rotated.
- Is the endpoint URL and method right? A POST sent to a GET path fails in a way that looks like a key problem.
- Is your timeout too tight? If your client gives up before the job completes, you see a timeout that has nothing to do with the model.
Then check the request shape
- Status code family tells you where to look. A 4xx is almost always your request; a 5xx is usually transient and worth a bounded retry.
- Read the error message literally. Print the response body before you raise. The field named in the message is the field to fix.
- Rate or quota responses mean slow down and retry with backoff, not rewrite the prompt.
Finally, check the prompt
If the call succeeds but the image is wrong, you are in prompt territory, not error territory:
- Subject missing or generic? Move it to the first clause and make it concrete.
- Composition off? Fix aspect ratio and framing words together; do not patch one with the other.
- Artifacts recurring? Add a short, specific negative.
- Contradictions? Look for a negative that fights the positive and delete it.
Keep one habit above all: log the exact payload, the response status, and the resulting image reference for every generation you care about. Reproducibility turns debugging from guesswork into a diff. Build your first request, confirm the output, then tighten the prompt before you ship — and keep the payload you shipped so the next change is measured against something real.
Related articles
View all articles →-
Tutorial
gemini-omni-1.2-flash in practice: 3 video generation business scenarios and a go-live checklist
Three application scenarios: text-based creative, first-frame animation, and reference-image-constrained visuals
-
Tutorial
How to Call gpt-image-2.5-sunburst/text-to-image via the HiAPI API: curl and Python
An integration walkthrough with a working request, reference-image handling, and retry patterns
9 min read -
Tutorial
How to Call gpt-5.6-terra@azure via the HiAPI API: curl and Python Guide
Build a working request, add streaming and tool calls, and handle errors without guesswork
9 min read