Tutorial
How to Call seedream-5.0-lite/text-to-image via the HiAPI API: curl and Python Guide
A working request walkthrough for developers integrating text-to-image through HiAPI
What this integration actually does
A text-to-image call on HiAPI is a short conversation between your code and an asynchronous job queue. You send one request that describes the image you want; the platform validates it, accepts the job, and hands you back an identifier. The image is not in that first response — it is produced later. Your code then watches the job until it reports completion, and finally downloads the image from the URL the job exposes.
That shape matters because it determines how you write the integration. There are three distinct moments: submission, polling, and retrieval. Each has its own failure modes. Submission fails on malformed payloads or an invalid model path. Polling fails when the loop is wrong — too fast, too short, or unable to tell "still running" from "finished." Retrieval fails when you treat the output URL as permanent or forget to write the bytes to disk.
Underneath, seedream-5.0-lite/text-to-image is a general-purpose text-to-image model on the HiAPI platform. You address it by that exact model identifier. The parameters you send are the ordinary ones for this kind of call: the model name, a prompt, and whatever output size or count controls the endpoint documents. You do not need any special SDK; a plain HTTPS client is enough.
By the end of this walkthrough you will have a request you can reproduce with curl, a Python function around it, a sense of how to phrase prompts so rendered text comes out legible, and a polling loop that actually terminates. Everything runs through the same HiAPI endpoint, so once one call works, the rest is parameter tuning.
Build the request with curl
Start from the command line. A working curl invocation is the ground truth for the payload — once it succeeds, every other language is a translation exercise.
You need two things before the first call: your HiAPI API key and the base URL for the API. The key goes in an Authorization header as a bearer token. Keep it out of your shell history where you can; an environment variable is the simplest habit.
export HIAPI_API_KEY="your_api_key_here"
The request itself is a POST with a JSON body. The model identifier is the path segment you are calling, and the prompt describes the image:
curl -sS -X POST "$HIAPI_BASE_URL" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "seedream-5.0-lite/text-to-image",
"prompt": "A minimalist poster for a coffee shop called North Roast, warm cream background, bold dark lettering"
}'
Set HIAPI_BASE_URL to the endpoint shown in the platform documentation for text-to-image submissions; the path differs by product surface, so copy it rather than guessing.
Read the response before writing any more code. A successful submission returns a JSON object with a job identifier and a status field. The status will typically indicate that the job is queued or processing — not finished. That is expected. Note the field names exactly as they come back; you will reference them in the next two sections.
Two curl-level mistakes account for most first failures. The first is a missing or mis-scoped key: the response is an authentication error, not a model error. The second is a malformed body — an unescaped quote inside the prompt, a trailing comma, single quotes where JSON requires double. If the platform rejects the payload, pretty-print it with jq and validate it separately before blaming the model.
When the submission returns an identifier, you have a valid request. Everything after this is waiting and fetching.
Port it to Python
The Python version should be a small, reusable function — not a script that hardcodes one prompt. Use requests (or httpx if you prefer); nothing about the call requires a special client.
import os
import requests
API_KEY = os.environ["HIAPI_API_KEY"]
BASE_URL = os.environ["HIAPI_BASE_URL"]
HEADERS = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
def submit_image(prompt: str, **params) -> dict:
payload = {
"model": "seedream-5.0-lite/text-to-image",
"prompt": prompt,
**params,
}
response = requests.post(BASE_URL, headers=HEADERS, json=payload, timeout=30)
response.raise_for_status()
return response.json()
Three deliberate choices here. The key and base URL come from the environment, so the function is safe to commit. The **params catch-all lets you pass output size or other documented options without editing the function. And raise_for_status() turns HTTP errors into exceptions immediately, which is what you want — a 4xx from submission will never become a valid job, so failing fast beats inspecting a confusing response body.
The returned dict is your job record. Print it once during development so you know the exact key names for the identifier and status. Resist the urge to index into it blindly; a small accessor keeps the rest of the code readable:
job = submit_image("A flat illustration of a paper plane over a city map")
print(job)
If that prints a queued job, the translation from curl is correct. If it raises, the exception message from raise_for_status() tells you whether you have an auth problem or a payload problem — the same two categories you would have hit in the shell.
Prompting for web knowledge and rendered text
Text-to-image models are strongest when the prompt describes what should be visible, and weakest when it implies knowledge the model has to infer. seedream-5.0-lite/text-to-image can lean on general world context — recognizable objects, common design conventions, typographic styles — but you still have to give it enough to anchor that context.
For scene prompts, describe the subject, the composition, and the visual register. "A cozy reading nook by a window, soft afternoon light, three-quarter view, muted earth tones" tells the model both what to draw and how to style it. Vague prompts produce average images; specific prompts produce images you can use.
Rendered text is where prompts usually break. If you need words inside the image, follow a few rules:
- Quote the exact string. Write the text you want in quotes so it is clearly a literal, not a description.
- Keep it short. A short headline or a brand name renders reliably; a paragraph does not.
- State placement and style. "The word 'North' centered at the top in a bold sans-serif" beats "some text at the top."
- Avoid competing text. Multiple distinct text elements in one image invite garbling. If you need more than one, keep them short and far apart.
- Iterate one variable at a time. Change the wording, not the wording plus the layout plus the style, or you will not learn what fixed it.
For anything involving recognizable context — a product category, a genre of poster, a type of signage — name it explicitly. The model's prior knowledge helps most when the prompt gives it a hook. "A vintage travel poster for a coastal town" carries more usable context than "a poster."
Do not invent parameters. If the prompt is not producing the text placement you want, adjust the wording of the prompt itself before reaching for an option you have not seen documented.
Handle async jobs and download the output
Polling is where integrations quietly go wrong. The job is asynchronous, so you need a loop that checks status, waits between checks, gives up eventually, and knows exactly which status values mean "done."
import time
def wait_for_job(job_id: str, poll_interval: float = 3.0, timeout: float = 180.0) -> dict:
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
response = requests.get(
f"{BASE_URL}/{job_id}", headers=HEADERS, timeout=30
)
response.raise_for_status()
job = response.json()
status = job.get("status")
if status in {"succeeded", "completed"}:
return job
if status in {"failed", "error", "cancelled"}:
raise RuntimeError(f"Job {job_id} ended with status {status}")
time.sleep(poll_interval)
raise TimeoutError(f"Job {job_id} did not finish within {timeout}s")
Adjust the polling path and the status strings to whatever the platform documentation shows — those are the two details most likely to differ from the sketch above. The structure is what matters: a monotonic deadline, a sleep between checks, explicit success and failure sets, and a timeout that raises rather than returning a half-finished job.
Once the job reports success, the response contains a URL for the output. Treat that URL as temporary. Download the bytes immediately rather than storing the link:
def download_image(url: str, path: str) -> None:
response = requests.get(url, timeout=60)
response.raise_for_status()
with open(path, "wb") as f:
f.write(response.content)
job = wait_for_job(job_id)
download_image(job["output"]["url"], "out.png")
Confirm the file is a real image before moving on — open it, check its dimensions, or at minimum check that the byte length is not suspiciously tiny. A truncated download looks like a success to requests if the server closed cleanly.
Common errors and how to fix them
Most failures in a first integration fall into four buckets. Work through them in order.
The submission is rejected
If the first call returns a 4xx, print the response body. An authentication error means the key is missing, expired, or not being sent as a bearer token. A schema error means the payload is malformed — the model field is wrong, the prompt is missing, or a documented parameter is misspelled. Validate the JSON separately and diff it against a working curl example.
The job never completes
The loop spins forever or times out. Check the polling interval first: hammering the endpoint with no sleep can trip rate limits and make a healthy job look stuck. Then check your status comparison — if the platform returns success and you are matching succeeded, every job looks unfinished. Log the raw status value once instead of guessing.
The output URL returns a 404 or 403
Output URLs are signed and time-limited. If you polled for a long time and then fetched, the link may have expired. Fetch immediately after completion, and if you need to keep the result, save the file rather than the link.
The image renders but the text is garbled
This is a prompting problem, not a code problem. Shorten the text, quote it exactly, reduce the number of text elements, and make the style instruction more explicit. Re-run with one change at a time so you know which adjustment helped.
Requests hang or time out
Always set explicit timeouts on both submission and polling calls. A request with no timeout can block a worker indefinitely, and a polling loop without a deadline turns a transient outage into a stuck process.
When something behaves unexpectedly, reduce it to the smallest curl call that still fails. If the raw HTTP request misbehaves, the Python layer is not the problem — and once curl works again, the port back to Python is mechanical. The model page for seedream-5.0-lite/text-to-image lists the current parameters and response shape, so check there before assuming a field name.
Related articles
View all articles →-
Tutorial
How to Call hailuo-2.3-fast/image-to-video via the HiAPI API: curl and Python
A first working request for the fast, low-cost image-to-video tier — from model ID to downloaded MP4
9 min read -
Tutorial
How to Use hailuo-2.3/text-to-video via the HiAPI API: curl, Python, and a Working Request
11 min read -
Tutorial
Kandinsky 6.0 Pro Text-to-Video on HiAPI: curl and Python Tutorial
A request-first walkthrough: build the prompt-to-clip call, submit it, poll for completion, and download the result
11 min read