HiAPI
  • Models
  • Pricing
Search

Search HiAPI models, tools, and resources.

  • Models
  • Pricing
HiAPI

One API, All AI Models

Generate images, video, and audio with leading models through one production-ready API.

Get a free API key

AI Image API

  • All image models
  • GPT Image 2
  • Nano Banana 2
  • Seedream 5.0 Pro
  • Qwen Image 2.0 Pro
  • FLUX 1.1 Pro

AI Video API

  • All video models
  • Seedance 2.5
  • FLUX.3 Video
  • Seedance 2.0
  • Veo 3.1
  • Kling 3.0 Omni

AI Audio API

  • All audio models
  • MiniMax Music 2.6
  • MiniMax Music 1.5
  • ElevenLabs v3
  • Text to music
  • Text to speech

Product

  • Model marketplace
  • Playground
  • Pricing
  • Image API Cost Calculator
  • Free GPT Image 2 Generator
  • Free Nano Banana Image Generator
  • Outfit Preview
  • Product Photo Lab

Developers

  • Documentation
  • API Reference
  • Agent Skills
  • LLM integration index
  • Blog

Company

  • About
  • Contact support
  • Terms of Service
  • Privacy Policy

© 2026 hiapi. All rights reserved.

Open source on GitHubPython SDK on PyPI
  • Why expand prompts at all?
  • The expander: one system prompt per model family
  • Before / after
  • The full pipeline: expand, create task, poll, download
  • Watch the input schemas — they differ per model
  • Production hardening
  • Where to go next
  • FAQ
TutorialJul 9, 2026

Build an AI Image Prompt Enhancer: LLM Prompt Expansion for ideogram-v4 and flux-2

hiapiimage-generationprompt-engineeringtutorialapi-examples

Latest models

Explore models

Contents
  • Why expand prompts at all?
  • The expander: one system prompt per model family
  • Before / after
  • The full pipeline: expand, create task, poll, download
  • Watch the input schemas — they differ per model
  • Production hardening
  • Where to go next
  • FAQ

Generate it with HiAPI

Choose a model, enter your prompt, and see the result.

HiAPI Blog

Related articles

HiAPI

Generate it with HiAPI

Short prompts produce generic images. "coffee shop logo" gives the model almost nothing to work with — no name to render, no style, no composition, no palette. Modern image pipelines solve this with prompt expansion: a cheap LLM pass that turns a five-word prompt into a rich, model-appropriate paragraph before it hits the image API. Some platforms now bake an expander into specific models, which tells you how standard this preprocessing step has become.

This recipe builds that step yourself, as a small Python pipeline: an LLM enriches the user's short prompt, then the result goes to ideogram-v4 or flux-2 through hiapi's unified task API. You control the expansion rules per model — which matters, because what makes a great Ideogram prompt (exact text in quotes) is different from what makes a great FLUX.2 prompt (scene, lighting, camera).

Prerequisites: a hiapi API key (create one in the dashboard), Python 3.10+ with requests (pip install requests), and access to any chat-completions-compatible LLM endpoint for the expansion step (a small/cheap model is plenty — the expander only writes ~100 words at a time).

Why expand prompts at all?

Two of the models on hiapi ship their own built-in expansion switch: qwen-image-2.0 has prompt_extend and flux-1.1-pro has prompt_upsampling. Flip those on and the platform enriches the prompt server-side.

But ideogram-v4 and flux-2/text-to-image — two of the strongest models for typography and prompt adherence respectively — expose no built-in expander. Their input schemas accept a prompt string and generation parameters, nothing else. If you want enriched prompts on these models, you bring your own expansion step. The upside: your own expander is model-aware, brand-aware, and debuggable. You see exactly what string was sent.

The expander: one system prompt per model family

The whole trick is a string-in, string-out function. Here it is with a provider-agnostic LLM call — point LLM_BASE_URL / LLM_API_KEY / LLM_MODEL at whatever chat-completions-compatible endpoint you already run:

import os
import requests

LLM_BASE_URL = os.environ["LLM_BASE_URL"]   # e.g. https://your-llm-endpoint/v1
LLM_API_KEY = os.environ["LLM_API_KEY"]
LLM_MODEL = os.environ["LLM_MODEL"]

EXPANDER_PROMPTS = {
    # ideogram-v4 is a typography specialist: exact strings to render go in
    # double quotes, and layout/style language matters more than camera language.
    "ideogram-v4": (
        "You expand short image prompts for a typography-focused image model. "
        "Rules: keep the user's intent exactly; if the image should contain text, "
        "put the exact words in double quotes; describe layout (centered, badge, "
        "poster), lettering style, palette, and background; flat/vector terms are "
        "good; no camera or lens language; output 60-100 words, one paragraph, "
        "no preamble."
    ),
    # flux-2 has strong prompt adherence for scenes: subject, setting, lighting,
    # mood, camera. It has no negative prompt, so state what you want, not what
    # you don't.
    "flux-2/text-to-image": (
        "You expand short image prompts for a high-fidelity text-to-image model. "
        "Rules: keep the user's intent exactly; add subject detail, setting, "
        "lighting, mood, composition and camera hints; state desired qualities "
        "positively (the model has no negative prompt); output 80-120 words, one "
        "paragraph, no preamble."
    ),
}

def expand_prompt(short_prompt: str, target_model: str) -> str:
    """LLM pass: short user prompt -> enriched, model-appropriate prompt."""
    r = requests.post(
        f"{LLM_BASE_URL}/chat/completions",
        headers={"Authorization": f"Bearer {LLM_API_KEY}"},
        json={
            "model": LLM_MODEL,
            "messages": [
                {"role": "system", "content": EXPANDER_PROMPTS[target_model]},
                {"role": "user", "content": short_prompt},
            ],
            "temperature": 0.7,
        },
        timeout=60,
    )
    r.raise_for_status()
    return r.json()["choices"][0]["message"]["content"].strip()

Because the pipeline only needs str -> str, you can swap this for any expander — a bigger LLM, a fine-tuned one, or even a deterministic template for fixed brand campaigns.

Before / after

Input: coffee shop logo

Expanded for ideogram-v4:

A minimalist circular badge logo for a specialty coffee shop named "Driftwood Coffee", with the words "DRIFTWOOD COFFEE" in clean uppercase serif lettering curving around a simple line-art coffee cup with three steam lines. Warm cream background, dark espresso-brown lettering and linework, flat vector style, high contrast, centered composition with generous margins.

Expanded for flux-2:

A cozy specialty coffee shop interior at golden hour, warm sunlight streaming through large front windows onto a walnut counter, a barista pouring latte art in a ceramic cup, shelves of amber glass jars and burlap coffee sacks behind, soft steam rising, shallow depth of field, 35mm photography look, warm inviting tones, sharp focus on the cup.

Same five-character intent, two completely different — and correctly targeted — prompts. The Ideogram version quotes the exact text to render (its specialty); the FLUX.2 version spends its words on scene and light (its specialty).

The full pipeline: expand, create task, poll, download

All hiapi generation models are called through one async endpoint: POST /v1/tasks creates a task, GET /v1/tasks/<id> polls it, and the finished asset appears at data.output[0].url.

import json
import os
import time
import requests

HIAPI_KEY = os.environ["HIAPI_API_KEY"]
TASKS = "https://api.hiapi.ai/v1/tasks"
HEADERS = {"Authorization": f"Bearer {HIAPI_KEY}", "Content-Type": "application/json"}

def create_task(model: str, input_obj: dict) -> str:
    r = requests.post(TASKS, headers=HEADERS,
                      json={"model": model, "input": input_obj}, timeout=60)
    data = r.json()
    task_id = (data.get("data") or {}).get("taskId")
    if not task_id:
        raise RuntimeError(f"create task failed: {json.dumps(data)[:300]}")
    return task_id

def wait_task(task_id: str, timeout_s: int = 300, poll_s: int = 5) -> dict:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        r = requests.get(f"{TASKS}/{task_id}",
                         headers={"Authorization": f"Bearer {HIAPI_KEY}"}, timeout=30)
        task = r.json().get("data") or {}
        if task.get("status") == "success":
            return task
        if task.get("status") == "fail":
            err = task.get("error") or {}
            raise RuntimeError(f"task failed: {err.get('code')} {err.get('message')}")
        time.sleep(poll_s)
    raise TimeoutError(f"task {task_id} still running after {timeout_s}s")

def generate(short_prompt: str, target_model: str) -> str:
    expanded = expand_prompt(short_prompt, target_model)
    print(f"expanded prompt -> {expanded}\n")

    if target_model == "ideogram-v4":
        input_obj = {
            "prompt": expanded,
            "rendering_speed": "BALANCED",  # required: TURBO | BALANCED | QUALITY
            "aspect_ratio": "1:1",          # 1:1, 16:9, 4:3, 9:16, 3:4
        }
    else:  # flux-2/text-to-image
        input_obj = {
            "prompt": expanded,
            "aspect_ratio": "3:2",          # 1:1, 16:9, 3:2, 2:3, 4:5, 5:4, 9:16, 3:4, 4:3, custom
            "resolution": "1 MP",           # '0.5 MP' | '1 MP' | '2 MP' | '4 MP'
        }

    task_id = create_task(target_model, input_obj)
    task = wait_task(task_id)
    url = task["output"][0]["url"]

    # Output URLs expire — download immediately, don't hot-link them.
    img = requests.get(url, timeout=120).content
    fname = f"{target_model.replace('/', '-')}.png"
    with open(fname, "wb") as f:
        f.write(img)
    return fname

if __name__ == "__main__":
    print(generate("coffee shop logo", "ideogram-v4"))
    print(generate("coffee shop logo", "flux-2/text-to-image"))

Run it:

export HIAPI_API_KEY="sk-..."
export LLM_BASE_URL="https://your-llm-endpoint/v1"
export LLM_API_KEY="..."
export LLM_MODEL="your-small-model"
python pipeline.py

Watch the input schemas — they differ per model

The task API validates input strictly per model, and unknown fields are rejected, not ignored. If you send ideogram-v4 a field it doesn't know:

{
  "code": 400,
  "error_code": "INVALID_REQUEST",
  "message": "invalid input: <root>: additional properties 'size' not allowed"
}

Three differences that matter in this pipeline:

  • rendering_speed is required on ideogram-v4 (TURBO/BALANCED/QUALITY — pricing differs per tier). flux-2 has no such field.
  • Aspect-ratio menus don't match. ideogram-v4 offers five ~1MP presets (1:1, 16:9, 4:3, 9:16, 3:4); flux-2 offers a wider set plus custom with explicit width/height (256–2048, multiples of 16). If your app lets users pick a ratio, map it per model before dispatch.
  • flux-2 sizes by megapixel tier (resolution: '0.5 MP' to '4 MP'), and has no negative prompt — another reason the expander should phrase everything positively.

Production hardening

Prefer callbacks over polling at volume. Add a callback block when creating the task and hiapi POSTs you the terminal state instead of you polling:

payload = {
    "model": "flux-2/text-to-image",
    "input": input_obj,
    "callback": {"url": "https://your-domain.com/hiapi/callback", "when": "final"},
}

Polling is simpler and fine for local scripts, cron jobs, and low volume; callbacks avoid burning requests on long generations and give you exactly one notification per task. A common production pattern is callbacks as the primary channel with a slow reconciliation poll as backup.

Make retries idempotent. Send an Idempotency-Key header (up to 255 bytes, kept 24 h) when creating tasks. Retrying with the same key returns the original taskId (with an Idempotent-Replay: true response header) instead of creating — and billing — a duplicate task. Reusing a key with a different body returns 422 IDEMPOTENCY_KEY_MISMATCH; a retry that races the in-flight original gets 409 with Retry-After. Derive the key from your own job ID:

headers = {**HEADERS, "Idempotency-Key": f"expand-pipeline-{job_id}"}

Handle auth failures explicitly. A bad or under-privileged key fails with HTTP 401 and "code": "permission_denied" in the error body — catch it separately from generation errors, since retrying won't help:

if r.status_code == 401:
    raise SystemExit("hiapi key invalid or lacks access to this model")

Persist outputs immediately. Task output URLs carry an expiry. Download the bytes as soon as the task succeeds and store them yourself.

Keep the expander on a leash. Cap the expansion at ~120 words, pass temperature ≤ 0.8, and log both the short and expanded prompt with the taskId. When a generation looks wrong, the first question is always "what did the model actually receive?" — with your own expander, you can answer it.

Mind the cost stack. You're paying for two calls per image now: a small LLM completion (typically negligible) plus the image generation — ideogram-v4 bills per image by rendering_speed tier, flux-2 per image by resolution tier. Current rates for both are on the pricing page.

Where to go next

  • Model details and parameters: ideogram-v4 and flux-2 model pages
  • Skip the DIY expander where the platform has one built in: qwen-image-2.0 (prompt_extend) and flux-1.1-pro (prompt_upsampling)
  • Task API reference and more integration guides: docs
  • Compare per-image pricing across models: pricing

FAQ

Can I send the same expanded prompt to both models? You can, but you'll leave quality on the table. Ideogram rewards quoted literal text and layout language; FLUX.2 rewards scene, lighting, and camera detail. Keeping one expander system prompt per model family costs a few lines and pays off every generation.

Do any hiapi models expand prompts for me? Yes — qwen-image-2.0 accepts prompt_extend and flux-1.1-pro accepts prompt_upsampling. For ideogram-v4 and flux-2/text-to-image there is no built-in expander, which is exactly when this recipe applies.

How long should the expanded prompt be? 60–120 words is the sweet spot for this pipeline. Past that you dilute the signal, and some budget models truncate long prompts hard (flux-schnell, for instance, cuts around 256 tokens). Density beats length: every added phrase should constrain the image.

Does flux-2 support negative prompts? No. State what you want, not what you don't — and encode that rule in the expander's system prompt so it never emits "no text, no watermark"-style phrasing that the model would read as content.

What if the LLM changes the user's intent? Constrain it in the system prompt ("keep the user's intent exactly"), keep temperature moderate, and log the before/after pair. For high-stakes flows, show users the expanded prompt before generating, or offer a "use my prompt verbatim" toggle that bypasses the expander.

Why did my request fail with a 400 before any generation happened? Schema validation. Each model validates input strictly — a missing required field (like rendering_speed on ideogram-v4) or an unknown field fails fast with error_code: "INVALID_REQUEST" and a message naming the offending field. These failures are free; fix the field and resubmit.

Latest models

View all models
  • GPT Image 2From $0.030/image
  • Nano Banana 2From $0.051/image
  • Seedream 5.0 ProFrom $0.050/image
  • Seedance 2.5From $0.231/s

Explore models

TextImageVideoAudio
Back to blog
GPT Image 2From $0.030/image
Nano Banana 2From $0.051/image
Seedream 5.0 ProFrom $0.050/image
Seedance 2.5From $0.231/s
View all models
TextChat and reasoning
ImageGenerate and edit
VideoText and image to video
AudioSpeech and music
Start generating
View model pricing
View all articles
How to Use the flux-3 API for Text-to-Video, Audio, and Continuation

How to Use the flux-3 API for Text-to-Video, Audio, and Continuation

minimax-music-3 API: curl & Python Guide

minimax-music-3 API: curl & Python Guide

How to use grok-imagine-image-2.0/image-to-image via the hiapi API: curl, Python, and a working request

How to use grok-imagine-image-2.0/image-to-image via the hiapi API: curl, Python, and a working request

How to Use grok-imagine-image-2.0/text-to-image via the hiapi API: curl, Python, and a Working Request

How to Use grok-imagine-image-2.0/text-to-image via the hiapi API: curl, Python, and a Working Request

How to Use the qwen-image-3.0 API: curl, Python, and a Working Request

How to Use the qwen-image-3.0 API: curl, Python, and a Working Request

How to Use qwen-image-3.0-pro via the hiapi API: curl, Python, and a Working Request

How to Use qwen-image-3.0-pro via the hiapi API: curl, Python, and a Working Request

Start generating