Search

Search HiAPI models, tools, and resources.

Back to blog
Tutorial

How to use ideogram-v4 via the hiapi API: curl, Python, and a working request

hiapiideogram-v4Image APITutorialTypography

Ideogram models built their reputation on one thing most image models still fumble: typography. Posters where the headline is spelled correctly, logos with clean lettering, signage that doesn't dissolve into alphabet soup. ideogram-v4 is available on hiapi through the same unified task API as every other image and video model, and this guide walks through the exact request: a copy-paste curl version, a complete Python script, the parameters the schema actually validates (they are not the same surface as Ideogram's native API), and the errors you'll hit in practice.

What you're building, and what you need

Goal: send a text prompt to POST /v1/tasks, wait for the task to finish, and download an image whose text renders the way you asked.

You need exactly one thing: a hiapi API key — create one in the dashboard. It's used as a Bearer token on every request. There is no free unauthenticated tier; every call below assumes Authorization: Bearer sk-....

Like all models on hiapi, ideogram-v4 is asynchronous: you create a task, get a taskId back immediately, and then either poll for the result or receive a callback. There is no synchronous "wait 40 seconds on one HTTP connection" mode — and you don't want one for image generation anyway.

The minimal request: curl

Create the task:

curl -X POST https://api.hiapi.ai/v1/tasks \
  -H "Authorization: Bearer $HIAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "ideogram-v4",
    "input": {
      "prompt": "A vintage art-deco poster for a jazz festival. Headline text \"MIDNIGHT & BRASS\" in bold gold lettering on deep navy, subtitle \"Live at the Riverside, Fridays\" in a thin sans-serif underneath.",
      "rendering_speed": "BALANCED",
      "aspect_ratio": "3:4"
    }
  }'

The response comes back immediately with a task id in data.taskId (it looks like tk-hiapi-...). Poll it:

curl https://api.hiapi.ai/v1/tasks/tk-hiapi-XXXXXXXX \
  -H "Authorization: Bearer $HIAPI_KEY"

While the image is being generated, data.status moves through queued and handling. On a terminal state it becomes success or fail. On success, the image URL is at data.output[0].url:

{
  "code": 200,
  "data": {
    "taskId": "tk-hiapi-XXXXXXXX",
    "status": "success",
    "output": [
      { "url": "https://.../result.png?...expireAt=..." }
    ]
  }
}

One thing to notice in that URL: it's signed and it expires (expireAt). Download the bytes as soon as the task succeeds and store them on your own storage. Never save the hot link in your database.

The parameters ideogram-v4 actually accepts

This is the part worth being precise about. If you've read Ideogram's own documentation, you know its native API exposes style presets, magic-prompt toggles, negative prompts, and batch counts. hiapi's task schema for ideogram-v4 is deliberately smaller, and the validator rejects anything outside it. Here is the full surface, confirmed against the live API:

FieldRequiredType / values
promptyesstring
rendering_speedyes"TURBO", "BALANCED", "QUALITY"
aspect_rationo"1:1", "16:9", "4:3", "9:16", "3:4"
seednointeger

Both required fields are enforced. Send an empty input and you get:

invalid input: prompt: missing required field "prompt";
rendering_speed: missing required field "rendering_speed"

Misspell an enum value and the error tells you the legal set:

invalid input: rendering_speed: value must be one of 'TURBO', 'BALANCED', 'QUALITY'

And anything from Ideogram's native surface that isn't in the table above is rejected as an unknown property — style_type, magic_prompt, negative_prompt, num_images, size, and image inputs all return a 400 like:

invalid input: <root>: additional properties 'style_type' not allowed

Practical consequences:

  • Typography control lives entirely in the prompt. Put the exact text you want rendered in double quotes inside the prompt, and describe the lettering ("bold art-deco lettering", "thin geometric sans-serif", "hand-painted sign lettering"). This is how you get the poster/logo/signage results Ideogram is known for — there is no separate style parameter to lean on.
  • One image per task. There's no n or num_images. If you want four candidates, submit four tasks and poll each taskId.
  • seed is your reproducibility knob. Same prompt + same seed gives you a stable starting point for iterating on wording.
  • Text-to-image only. The schema does not accept reference images on this model. If your workflow needs image-to-image, pick a model that supports it — see how to call multiple image models with one API key for switching models without changing your integration.

On rendering_speed: TURBO is the fast tier for drafts while you iterate on prompt wording; QUALITY is the slow, careful tier for the final render; BALANCED is the sensible default. Pricing is usage-based per image — check current numbers on the pricing page.

A complete Python script

The same flow — create, poll, download — with timeouts and terminal-state handling. Only dependency is requests (pip install requests).

import os
import time
import requests

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

def create_task(prompt: str, rendering_speed: str = "BALANCED",
                aspect_ratio: str = "1:1", seed: int | None = None) -> str:
    input_obj = {
        "prompt": prompt,
        "rendering_speed": rendering_speed,
        "aspect_ratio": aspect_ratio,
    }
    if seed is not None:
        input_obj["seed"] = seed
    resp = requests.post(API_BASE, headers=HEADERS,
                         json={"model": "ideogram-v4", "input": input_obj},
                         timeout=60)
    data = resp.json()
    task_id = (data.get("data") or {}).get("taskId")
    if not task_id:
        raise RuntimeError(f"create failed: HTTP {resp.status_code} {data}")
    return task_id

def wait_task(task_id: str, timeout_s: int = 600, poll_interval: int = 5) -> dict:
    deadline = time.time() + timeout_s
    while time.time() < deadline:
        resp = requests.get(f"{API_BASE}/{task_id}", headers=HEADERS, timeout=30)
        task = resp.json().get("data") or {}
        status = task.get("status")
        if status == "success":
            return task
        if status == "fail":
            err = task.get("error") or {}
            raise RuntimeError(f"task failed: {err.get('code')} {err.get('message')}")
        time.sleep(poll_interval)
    raise TimeoutError(f"task {task_id} not done after {timeout_s}s")

if __name__ == "__main__":
    task_id = create_task(
        prompt=('Minimal logo for a coffee brand called "Driftwood", '
                'the word Driftwood in a warm serif wordmark, single-color, '
                'cream background'),
        rendering_speed="QUALITY",
        aspect_ratio="1:1",
        seed=42,
    )
    print("task:", task_id)
    task = wait_task(task_id)
    url = task["output"][0]["url"]
    png = requests.get(url, timeout=120).content   # signed URL expires - download now
    with open("driftwood-logo.png", "wb") as f:
        f.write(png)
    print("saved driftwood-logo.png,", len(png), "bytes")

Production notes: callbacks, idempotency, and errors

Polling vs. callback. For an interactive tool generating one image at a time, polling every few seconds is simpler and perfectly fine. For batch or server-side workloads, add a callback to the create request so hiapi POSTs the terminal result to you instead:

{
  "model": "ideogram-v4",
  "input": { "prompt": "...", "rendering_speed": "BALANCED" },
  "callback": { "url": "https://your.domain.example/hiapi/hook", "when": "final" }
}

Two constraints, both enforced at create time: the URL must be https:// and reachable from the public internet, and when accepts only "final" — the API literally responds invalid callback.when: only 'final' is supported if you try anything else. Make your handler idempotent on taskId, return 2xx fast, and keep a polling fallback for tasks whose callback never lands. If a callback doesn't arrive, work through why your hiapi task callback isn't firing before blaming the queue.

Idempotency. Creating a task is not idempotent — every successful POST /v1/tasks is a new task and a new charge. Store the taskId against your own job id the moment you receive it, and on any later retry, poll the stored taskId instead of resubmitting.

The errors you'll actually see.

  • 401 with "code": "permission_denied" — the key is invalid or can't use this model. The message says exactly that: "This API key cannot use the selected model. Please check permissions or use another key." Fix it in the dashboard, don't retry.
  • 400 with "error_code": "INVALID_REQUEST" — a validation failure. The message names the exact field and the legal values; read it, fix the payload. Retrying the same body will fail forever.
  • 404 "task not found" on GET /v1/tasks/{id} — wrong task id, or a task created with a different key.
  • fail status on a task that was accepted — inspect data.error.code / data.error.message from the poll response. These are safe to retry as a new task.

FAQ

Does ideogram-v4 on hiapi support image-to-image or reference images? No. The task schema for this model accepts only prompt, rendering_speed, aspect_ratio, and seed — any image-input field is rejected with a 400. Use a model with image-to-image support (e.g. the gpt-image-2 family) if you need to edit or restyle an existing image.

Which rendering_speed should I use? Iterate with TURBO while you're refining the prompt and the exact wording of the text, then re-render the winner with QUALITY and the same seed. BALANCED is a fine default when you only render once.

How do I make ideogram-v4 render exact text? Put the literal text in double quotes inside the prompt and describe the typography around it: headline text "SUMMER SALE" in heavy condensed sans-serif. Shorter strings render more reliably than full sentences; if a long line comes out mangled, split it into a headline and a subtitle.

Can I generate several images in one request? No — one task produces one image. Submit multiple tasks in parallel instead; each gets its own taskId and queues independently.

Why do I get 401 permission_denied even though my key works with other models? Model access is per-key. Check the key's model permissions in the dashboard, or create a new key with access to ideogram-v4.

How long is the output URL valid? It's a signed URL with an expireAt timestamp. Treat it as minutes, not days: download the image as soon as the task reaches success and store the file yourself.

What aspect ratios are available? Exactly five: 1:1, 16:9, 4:3, 9:16, and 3:4. There is no free-form size parameter — a request with size is rejected as an unknown property.

Generate it with HiAPI

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

HiAPI Blog

Related articles

HiAPI

Generate it with HiAPI

Start generating