Tutorial
How to Call recraft-v4-styles-pro/text-to-image on the hiapi API: curl and Python
What this endpoint does and when to use it
recraft-v4-styles-pro/text-to-image turns a text prompt into an image, and its distinguishing feature is style awareness: you can steer the visual language of the result with style references rather than relying on prompt wording alone. That makes it a practical choice when output has to feel consistent across many generations, not just look good once.
Typical places developers reach for it:
- Brand-consistent assets — hero images, social cards, and ad variants that all need to sit inside the same visual system.
- Product and marketing mockups — iterate on a prompt, keep the style fixed, and produce a family of on-brand variations.
- Pipeline steps — generation as one stage in a larger automation, where another service consumes the saved image.
One property shapes every code example below: image generation is asynchronous. A request submits a job and returns quickly; the image appears later. You will write a submit step and a poll step, and treat them as separate concerns.
Prepare your hiapi credentials and request basics
All calls go through the single hiapi endpoint surface, so you need three things set up before the first request:
- An API key from your hiapi account. Treat it like any other secret: load it from an environment variable, never hardcode it in a script you might share.
- The model identifier exactly as published:
recraft-v4-styles-pro/text-to-image. - A tool that speaks HTTP —
curlfor exploration, an HTTP library for anything durable.
export HIAPI_API_KEY="your-key-here"
Conventions worth internalizing early:
- Send JSON bodies with
Content-Type: application/json. - Authenticate with a bearer token header.
- Keep the model id, prompt, and style references as distinct fields in the payload rather than smuggling everything into one string — the poll step and your logs will be far easier to reason about.
For exact header names, base URL, and current field definitions, consult the hiapi API reference for this model; treat this tutorial as the shape of the call, and the reference as the source of truth for names.
A working curl request
Here is the submit call. Read it as three layers: transport, auth, and body.
curl -sS -X POST "$HIAPI_BASE_URL/generate" \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "recraft-v4-styles-pro/text-to-image",
"prompt": "minimalist product photo of a ceramic mug on a linen surface, soft daylight",
"style_references": []
}'
Line by line:
-X POST— job submission, not retrieval.Authorization: Bearer ...— the token comes from the environment, so the command is safe to paste into a terminal and safe to share minus the variable.-d '{...}'— the JSON body.modelis the exact identifier.promptis free text describing the scene.style_referencesis the hook for style control, discussed next.-sS— quiet progress meter, but still surface errors, so a failed call is visible.
A successful response is an acknowledgement, not an image: you get back an identifier for the job and a status. Capture that identifier — it is the only handle you need for polling. If you are exploring interactively, pipe the response through a JSON formatter so the job id and status stand out.
Passing style references with your prompt
The mental model is simple: the prompt says what, the style references say how it should look. Keep them separate in the payload and you keep your options open — you can re-run the same prompt against several style sets to compare results.
{
"model": "recraft-v4-styles-pro/text-to-image",
"prompt": "ceramic mug on a linen surface, soft daylight",
"style_references": [
"https://your-bucket.example.com/styles/mug-ref-01.jpg",
"https://your-bucket.example.com/styles/mug-ref-02.jpg"
]
}
Practical guidance:
- Start with no references. Run the prompt bare first. Then add one reference and observe the delta, then two. You learn more from one change at a time than from a full style set on the first attempt.
- Keep references reachable. Host them somewhere your service can fetch reliably — a public bucket or a signed URL with a comfortable expiry — rather than links that die after minutes.
- Keep the set coherent. References that agree with each other (similar lighting, palette, texture) tend to produce more stable results than a mixed bag where the model has to reconcile contradictions.
- Version your references. Store them under stable keys. When an output goes wrong next month, you need to know exactly which reference files were in play.
Check the hiapi reference for this model to confirm whether style references are passed as URLs, uploaded assets, or named presets, and what limits apply to their count and format.
The same call in Python
The curl call translates directly. Keeping it in a small function makes it reusable as a pipeline stage.
import os
import requests
HIAPI_BASE_URL = os.environ["HIAPI_BASE_URL"]
HIAPI_API_KEY = os.environ["HIAPI_API_KEY"]
def submit_generation(prompt: str, style_references: list[str] | None = None) -> str:
"""Submit a text-to-image job and return the job id."""
payload = {
"model": "recraft-v4-styles-pro/text-to-image",
"prompt": prompt,
"style_references": style_references or [],
}
response = requests.post(
f"{HIAPI_BASE_URL}/generate",
headers={
"Authorization": f"Bearer {HIAPI_API_KEY}",
"Content-Type": "application/json",
},
json=payload,
timeout=30,
)
response.raise_for_status()
data = response.json()
return data["job_id"]
Notes on the design:
- Always set a timeout. A submit that hangs forever will block your worker just as effectively as a crash.
raise_for_status()turns non-2xx responses into exceptions you can catch and log, instead of silently proceeding with a malformed body.- Return only the id. The job id is the contract between submission and polling. Keeping the rest of the response out of your calling code makes the next section easier to write.
Confirm the actual response field names in the hiapi reference and adjust data["job_id"] accordingly — field naming is the one thing most likely to differ from this sketch.
Polling for job completion
Generation takes time. Poll at a fixed interval, stop on a terminal state, and give up eventually.
import time
def wait_for_job(job_id: str, timeout: float = 300, interval: float = 3) -> dict:
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
response = requests.get(
f"{HIAPI_BASE_URL}/jobs/{job_id}",
headers={"Authorization": f"Bearer {HIAPI_API_KEY}"},
timeout=30,
)
response.raise_for_status()
job = response.json()
status = job["status"]
if status == "succeeded":
return job
if status == "failed":
raise RuntimeError(f"Job {job_id} failed: {job.get('error')}")
time.sleep(interval)
raise TimeoutError(f"Job {job_id} did not finish within {timeout}s")
The non-obvious parts:
- Three outcomes, not two. Pending, succeeded, failed. Treat anything unrecognized as pending with a log line, so a newly added status doesn't crash your worker.
- One deadline for the whole wait. A
time.monotonic()deadline is immune to clock adjustments, unlike wall-clock arithmetic. - Don't hammer. A few seconds between checks is plenty for image generation. If you must poll faster, back off between attempts rather than tightening the interval.
- Enforce the timeout. A job that never terminalizes should surface as an error you can alert on, not a loop that runs until the container is killed.
If you run many jobs, submit in batches and poll concurrently — one event loop or thread pool with staggered intervals — rather than spawning a poller per job in a tight loop.
Downloading and saving the output
A finished job gives you an image reference, typically a URL. Download it promptly, since these links may be time-limited.
def save_image(job: dict, path: str) -> None:
url = job["output"]["image_url"]
for attempt in range(3):
try:
with requests.get(url, stream=True, timeout=60) as r:
r.raise_for_status()
with open(path, "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
return
except requests.RequestException:
if attempt == 2:
raise
time.sleep(2 ** attempt)
Practices that pay off in production:
- Stream the download. Generated images are not tiny; loading them fully into memory for no reason is waste.
- Write to a temp path, then rename. A crash mid-write shouldn't leave a truncated file where your pipeline expects a valid image.
- Retry the download, not the generation. A flaky bucket fetch is cheap to retry; a wasted generation is not. Cap retries and use exponential backoff.
- Persist the job id alongside the file. When someone asks where an image came from, the answer should be one lookup away rather than a dig through logs.
For object storage, replace the open() with your SDK's streaming upload and keep the same retry envelope.
Errors, retries, and cost sanity checks
The failure modes you will meet first, and what to do about each:
- 401 / auth errors — the key is missing, mistyped, or not exported into the shell running the script. Check the environment before touching the payload.
- 4xx validation errors — usually a model id typo, a missing
prompt, or a malformed style reference. Log the response body verbatim; validation messages are the fastest fix here. - 429 / rate limits — back off with jitter rather than retrying immediately. A queue in front of your submit function beats a retry storm.
- Job timeouts — your poll deadline fired. Decide deliberately whether to resubmit; blind resubmission doubles spend without improving the odds.
- Transient network errors — retry submits and downloads with capped exponential backoff. Retry download failures freely; retry generations sparingly.
Cost discipline, in practical terms:
- Billing follows the work the model actually performs, so failed jobs and abandoned batches are the sneaky cost. Log every submit with its prompt hash and job id so you can account for volume later.
- Iterate with the cheapest possible prompt. Tune wording and style references on small, fast runs before committing to a large batch.
- Deduplicate. If the same prompt and style set is requested twice, serve the first result from your own cache.
- Add a hard concurrency cap in your worker. It is easier to raise a limit than to explain a surprise bill.
With submit, poll, and save as three separate, testable functions, the endpoint slots cleanly into any pipeline. For exact field names, statuses, and current pricing, check the recraft-v4-styles-pro/text-to-image model page on hiapi before you wire it in.
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