
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.
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.
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.
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:
| Field | Required | Type / values |
|---|---|---|
prompt | yes | string |
rendering_speed | yes | "TURBO", "BALANCED", "QUALITY" |
aspect_ratio | no | "1:1", "16:9", "4:3", "9:16", "3:4" |
seed | no | integer |
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:
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.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.
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")
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.model stringPOST /v1/tasks / GET /v1/tasks/{id} referenceDoes 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.
Key Takeaways