HiAPI
Blog

Tutorial

How to Use ideogram-v4.5 via the HiAPI API: curl, Python, and a Working Request

Oct 31, 2026 10 min read
封面图:浅灰蓝底上多层半透明玻璃片交叠,折射出克制的蓝色条带,安静而具建筑感。

What ideogram-v4.5 on HiAPI Gives You

ideogram-v4.5 is the model to reach for when the text in the image actually matters. Poster layouts, logo marks, packaging mockups, social cards — anything where a misspelled headline or a smeared caption ruins the asset — is where this model earns its place. If you are generating abstract textures or photographic scenery where nothing needs to be legible, a general-purpose image model will usually be cheaper and faster.

This article is an integration guide, not a model review. By the end you will have a request that runs, a Python client that does the same thing, and a reusable helper that handles the parts people usually get wrong: aspect ratio parameters, asynchronous job polling, and output retrieval.

One thing to internalize early: image generation on HiAPI is an asynchronous job API. You submit a request, get an identifier back, and poll until the job finishes. Code that assumes a response body already contains an image will not work. Every section below builds toward that pattern.

Before You Send a Request

Four things need to be in place before your first call.

1. An API key

Create one in your HiAPI account dashboard and keep it out of source control. Export it as an environment variable rather than pasting it into scripts:

export HIAPI_API_KEY="your_key_here"

2. The base URL

All requests go to the HiAPI API base URL, with the specific path for the generation endpoint appended. The platform documentation lists the current paths; do not hardcode a path you found in a blog post (including this one) if the docs say otherwise.

3. The model identifier

The model string you pass in the request body identifies ideogram-v4.5. Use the exact casing shown in the model list on the platform — model identifiers are matched literally, and a near-miss returns a validation error rather than a helpful suggestion.

4. The parameter reference

The model page for ideogram-v4.5 is the source of truth for which fields the request body accepts: prompt limits, supported aspect ratios, output formats, and any seed or style controls. Read it once before writing code. Parameters not listed there may be silently ignored or rejected, and guessing at values is the most common way to waste a debugging session.

With those four in hand, you are ready for the first request.

A Working curl Request

Here is a minimal submission. Substitute the endpoint path and model identifier from the platform docs if your account shows different values.

curl -X POST "$HIAPI_BASE_URL/<generation-path>" \
  -H "Authorization: Bearer $HIAPI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "ideogram-v4.5",
    "input": {
      "prompt": "A vintage travel poster for the Amalfi Coast, bold serif headline reading \"AMALFI\", warm sunset palette, grainy print texture"
    }
  }'

What each piece does:

  • Authorization — bearer token auth. A missing or malformed header is the most common cause of a 401.
  • Content-Type — must be application/json. Some HTTP clients do not set it automatically on POST, which produces confusing parse errors server-side.
  • model — the literal model identifier. Keep it at the top level of the body as shown in the docs.
  • input.prompt — your generation instruction. Quote text you want rendered inside the image, as above, so the model treats it as literal copy rather than description.
  • input.* — every other generation parameter (aspect ratio, format, seed, and so on) belongs inside this nested object alongside prompt.

A successful submission returns a JSON object containing a job identifier and a status field. There is no image in this response — that is expected. Save the identifier; you will need it in the polling step.

The Same Call in Python

The Python version is a one-to-one translation. We use requests and share the identical body so you can diff the two side by side.

import os
import json
import requests

API_KEY = os.environ["HIAPI_API_KEY"]
BASE_URL = os.environ["HIAPI_BASE_URL"]
GEN_PATH = "/<generation-path>"  # confirm in platform docs

payload = {
    "model": "ideogram-v4.5",
    "input": {
        "prompt": (
            "A vintage travel poster for the Amalfi Coast, bold serif "
            'headline reading "AMALFI", warm sunset palette, grainy print texture'
        )
    },
}

response = requests.post(
    BASE_URL + GEN_PATH,
    headers={
        "Authorization": f"Bearer {API_KEY}",
        "Content-Type": "application/json",
    },
    data=json.dumps(payload),
    timeout=30,
)
response.raise_for_status()

job = response.json()
print(job)

Two deliberate choices:

  • json.dumps instead of json= keeps the wire format visibly identical to the curl example. Either works; being explicit makes it easier to inspect what you actually sent when debugging.
  • timeout=30 on the submission only. Submission is fast; generation is not. Never let the submission call inherit a short timeout meant for polling, or you will report failures on jobs that are running fine.

The printed job dict contains the job identifier and current status. Store the identifier somewhere — the polling loop depends on it.

Controlling Aspect Ratio and Output Format

Aspect ratio and output format are request-body parameters, not separate endpoints. They live inside the same input object as the prompt:

{
  "model": "ideogram-v4.5",
  "input": {
    "prompt": "...",
    "aspect_ratio": "<ratio from docs>",
    "output_format": "<format from docs>"
  }
}

The important discipline here is not guessing values. Every generation model exposes a fixed enumeration of ratios and formats, and the accepted spellings differ between models — 16:9 versus 16x9, png versus PNG, and so on. Passing a value the model does not support typically yields a validation error or, worse, a silently defaulted output that looks like a bug in your prompt.

So the workflow is:

  1. Open the ideogram-v4.5 model page on the platform and copy the supported aspect ratio list and output format list verbatim.
  2. Put those values in a small allowlist in your code, so invalid input fails at the call site instead of at the API.
  3. Default to a ratio that matches your downstream layout. If the image will sit in a 1200×630 social card slot, choose a ratio close to that rather than generating square and cropping away content.

Typography-heavy images deserve one extra note: extreme ratios (very wide banners, very tall posters) compress the space available for rendered text. For headlines you actually need legible, mid-range landscape or portrait ratios are safer than panoramic extremes.

Handling Async Jobs and Downloading the Result

The lifecycle of a generation is three steps: submit → poll → fetch.

Submit

You already have this from the previous sections. The response gives you a job identifier and a status such as queued or processing.

Poll

Poll the job status endpoint with that identifier until the status reports completion. Two rules keep this civil:

  • Sleep between polls. Start around one second and back off. Hammering the status endpoint does not make the job finish sooner and risks rate limiting.
  • Bound the wait. Set a maximum number of attempts or a deadline, and fail loudly when it is exceeded rather than looping forever.
import time

def wait_for_job(job_id, headers, base_url, status_path,
                 max_attempts=60, initial_delay=1.0, factor=1.5):
    delay = initial_delay
    for _ in range(max_attempts):
        r = requests.get(
            f"{base_url}{status_path}/{job_id}",
            headers=headers,
            timeout=30,
        )
        r.raise_for_status()
        data = r.json()
        status = data.get("status")
        if status in ("succeeded", "completed", "success"):
            return data
        if status in ("failed", "error", "cancelled"):
            raise RuntimeError(f"job {job_id} ended with status {status}: {data}")
        time.sleep(delay)
        delay *= factor
    raise TimeoutError(f"job {job_id} did not finish in time")

Check the platform docs for the exact status strings and status path for your account — match on the documented values, and treat anything unrecognized as still-running rather than as success.

Fetch

When the job reports completion, the response contains the output location (typically a URL). Download it with a normal GET and write the bytes to disk. Output URLs may be time-limited, so download promptly instead of storing the URL for later use. Everything you needed to know to write this loop came from the submission response shape — there is no separate "get image" API to learn.

Wrapping It Into a Reusable Helper

Three inline scripts that each reimplement polling is a maintenance problem. Package it once.

import os, json, time, requests

class IdeogramClient:
    def __init__(self, base_url, api_key, gen_path, status_path,
                 poll_attempts=60, poll_factor=1.5):
        self.base_url = base_url.rstrip("/")
        self.gen_path = gen_path
        self.status_path = status_path
        self.poll_attempts = poll_attempts
        self.poll_factor = poll_factor
        self.headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json",
        }

    def generate(self, prompt, **params):
        body = {"model": "ideogram-v4.5", "input": {"prompt": prompt, **params}}
        r = requests.post(self.base_url + self.gen_path,
                          headers=self.headers,
                          data=json.dumps(body), timeout=30)
        r.raise_for_status()
        job = r.json()
        done = self._wait(job["id"])
        url = done["output"]["url"]  # confirm shape in docs
        return self._download(url)

    def _wait(self, job_id):
        delay = 1.0
        for _ in range(self.poll_attempts):
            r = requests.get(f"{self.base_url}{self.status_path}/{job_id}",
                             headers=self.headers, timeout=30)
            r.raise_for_status()
            data = r.json()
            if data.get("status") in ("succeeded", "completed", "success"):
                return data
            if data.get("status") in ("failed", "error", "cancelled"):
                raise RuntimeError(data)
            time.sleep(delay)
            delay *= self.poll_factor
        raise TimeoutError(job_id)

    def _download(self, url):
        r = requests.get(url, timeout=60)
        r.raise_for_status()
        return r.content

Usage is then a single call: img_bytes = IdeogramClient(...).generate("...", aspect_ratio="<from docs>"). Adjust the done["output"]["url"] access and the status strings to match the documented response shape — those are the two spots most likely to need tweaking per account.

Common Errors and Debugging Tips

Most first-week failures fall into five buckets.

401 / 403 — authentication. The key is missing, has whitespace from a copy-paste, or the Bearer prefix is absent. Print the first and last four characters of the key you are actually sending; a trailing newline from an environment file is a classic culprit.

400 — malformed JSON or unknown field. If the body was assembled by string concatenation, an unescaped quote in the prompt breaks the JSON. Build bodies as dicts and serialize with json.dumps. If the JSON parses, the next suspect is a parameter name that the model does not accept — compare your keys against the model page character by character.

404 / model-not-found. Usually the model identifier is misspelled or cased incorrectly, or the endpoint path is stale. Copy both from the platform docs rather than from memory or from an older tutorial.

429 — rate limited. Almost always polling too aggressively. Increase the initial poll delay and the backoff factor. On the submission side, add a retry with jitter for 429 and 5xx responses, but do not retry 4xx validation errors — they will fail identically every time.

Jobs that succeed but images you cannot open. Check that you downloaded bytes rather than a JSON error page; a raise_for_status() on the download GET catches most of these. Also verify you are treating the download URL as short-lived and not caching it across sessions.

When in doubt, log the full response body and status code of every call in the chain — submit, each poll, and download. The API is usually explicit about what went wrong; the bug is usually that we never looked.

With a working submission, a polling loop, and sane error handling in place, ideogram-v4.5 slots into a real pipeline just like any other HiAPI model call.

Related articles

View all articles →

Start building with persistent AI artifacts

Get your API key and call image, video and audio models through one task-based API.

Read the API docs