API Reference
Image
Generate an image from a prompt, or edit existing ones with up to four reference images.
/v1/images/generationsTwo routes over the same model: one starts from a prompt alone, the other starts from images you supply. Both return the image as base64 in the response body.
/v1/images/generations/v1/images/editsThese routes need a key from a stack on the image plan. A key from a
text plan gets 403 here, and an image key gets 403 on the chat
endpoints. Image generation runs on its own dedicated infrastructure, not
on the machine that answers chat.
Generation
application/json. A prompt in, a PNG out.
curl -X POST "https://api.trystac.com/v1/images/generations" \
-H "Authorization: Bearer $STAC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "a white dress shirt folded on a wooden table",
"size": "1024x1024",
"seed": 42
}'This route takes no input image. Sending one returns 400
wrong_route_for_reference_image — use /v1/images/edits instead.
Editing
multipart/form-data. One to four reference images, plus a prompt describing
the change.
curl -X POST "https://api.trystac.com/v1/images/edits" \
-H "Authorization: Bearer $STAC_API_KEY" \
-F "prompt=make it black and white" \
-F "size=1024x1024" \
-F "image[]=@photo.png"The file field accepts both image and image[] — the second is what
OpenAI's own examples use for multiple files. Up to 4 images, 5 MiB
each, in PNG, JPEG or WEBP. The format is read from the file's contents, not
from its extension or the part's Content-Type. The whole multipart body is
capped at 21 MiB.
Reference images are converted to RGB on arrival, which drops the alpha channel, colour profiles, and EXIF metadata — including any location data the original file carried.
model behaves differently on this route. Everywhere else in the API the
field is ignored and replaced with your stack's model. Here it is not —
rewriting it would mean re-encoding the whole multipart body. Omit the field,
or send exactly flux2-klein-4b. Any other value returns 404.
mask is not supported and returns 400. The pod loads a single pipeline, and
applying the edit to the whole image while pretending to honour a mask would be
worse than refusing.
Parameters
The same fields on both routes — as JSON on /generations, as form fields on
/edits.
promptstringRequiredWhat to generate, or what to change about the images you sent. It can be omitted if your API key has a prompt configured — see Prompt on the key. Read Prompt length before writing a long one.
sizestringOptionalDefault: 1024x1024Output dimensions — square, landscape, or portrait. A closed list; anything else returns 400.
1024x10241536x10241024x1536stepsintegerOptionalDefault: 4Denoising steps, from 1 to 8. The checkpoint is distilled, so it is tuned to produce a finished image in very few steps — this is why the ceiling is 8 and not 50.
guidance_scalenumberOptionalDefault: 1.0How strictly to follow the prompt, from 0 to 20.
seedintegerOptionalFrom 0 to 2^64−1. Omit it and the server draws one, returning it in meta.seed — that value is what reproduces the same image later.
nintegerOptionalDefault: 1Images per request. 1 is currently the only accepted value.
response_formatstringOptionalDefault: b64_jsonOnly b64_json. url is rejected — the image comes back in the response body.
Response
Identical on both routes.
{
"created": 1767225600,
"data": [{ "b64_json": "iVBORw0KGgo..." }],
"meta": {
"prompt": "a white dress shirt folded on a wooden table",
"width": 1024,
"height": 1024,
"steps": 4,
"guidance_scale": 1.0,
"seed": 8151234567890123456,
"n": 1,
"model": "flux2-klein-4b",
"timings": {
"queue_wait_s": 0.0100,
"decode_s": null,
"gpu_s": 4.1000,
"encode_s": 0.1800,
"worker_s": 4.2800
}
}
}meta reports the effective parameters — what the server actually used,
after defaults and after drawing a seed if you didn't send one.
data[].b64_jsonstringOptionalThe PNG, base64-encoded.
meta.seedintegerOptionalThe seed that produced this result. It belongs to the batch, not to an individual image: reproducing image i takes the same seed and the same n.
meta.timingsobjectOptionalWhere the time went, in seconds, to four decimal places. queue_wait_s is time spent waiting for a free slot; decode_s is reading your reference images (null on /generations, which has none); gpu_s is the diffusion itself; encode_s is producing the PNG. worker_s is the sum of the last three and excludes the queue wait.
Prompt length
The prompt is truncated at 512 tokens, silently. There is no error and no warning — anything past that point simply never reaches the model.
This is the most common cause of "the image ignored half of what I asked for". A long, detailed prompt is not just wasted past the cutoff; it also dilutes the part that does fit. Lead with the subject and the details that matter most.
Prompt on the key
The prompt can live on your API key, the way the system prompt does on the
text endpoints — and the same precedence applies.
| In the request body | On the key | What the model receives |
|---|---|---|
prompt with text | anything | the request's |
| absent or blank | configured | the key's |
| absent or blank | nothing | 400 missing_prompt |
That is what lets you send only the URL, the key and the image:
curl -X POST "https://api.trystac.com/v1/images/edits" \
-H "Authorization: Bearer $STAC_API_KEY" \
-F "image[]=@photo.png"It replaces, it does not append. There is no concatenation between the
key's prompt and the request's: sending prompt discards the configured one
entirely. And the key's text spends the same 512-token budget — a long prompt
on the key leaves no room for anything else.
Throughput
Image generation has no daily quota — diffusion produces no tokens, so the token budget that applies to the chat endpoints doesn't apply here. What limits you is the GPU, which renders one image at a time.
| Limit | What it means | |
|---|---|---|
| Submissions | 10 per minute | Shared between generations and edits |
| In flight | 3 at once | 1 rendering plus up to 2 waiting |
| Queue wait | 60 seconds | Past that the request returns 504 |
Both limits belong to the stack, not to the key. Issuing more keys doesn't raise them, because the same GPU does the work either way.
Firing 10 requests at once doesn't make them finish sooner — 3 are accepted
and 7 come back 429. The pattern that works is keeping 3 in flight and
starting the next one as each finishes.
Observed end to end: roughly 6–9s for text to image, and 7–21s for an edit, varying with the number, dimensions and weight of the reference images. "10 per minute" is a submission ceiling, not a promise of ten finished images per minute.
Storage and retention
Every generated image is stored, linked to the account, stack and key that
created it. This happens server-side and changes nothing in your code — the
response is still b64_json.
Two consequences worth knowing:
- A
200means the image was stored. The write happens before the response is sent, and adds a few hundred milliseconds to a call that already takes seconds. - A
502can happen with the image already generated. If storage fails we return the error rather than hand you an image while claiming it was saved. Retrying is safe — but it generates again, with a new seed unless you pinned one.
Files are kept for 30 days. If you need an image for longer, save the
b64_json on your side: this storage exists for traceability and for viewing
the image in the platform, not as permanent hosting.
Errors
Two error shapes reach you on these routes. The gateway's own rejections use
the API-wide { "detail": "..." } shape; anything the image server rejects
comes back in OpenAI's shape, with a stable code you can branch on:
{
"error": {
"message": "size must be one of: 1024x1024, 1536x1024, 1024x1536",
"type": "invalid_request_error",
"code": "invalid_size"
}
}| Status | code | Meaning |
|---|---|---|
400 | invalid_size, invalid_steps, invalid_n, invalid_guidance_scale, invalid_seed | Field outside its accepted range, or the wrong type |
400 | missing_prompt | prompt absent or empty, and no prompt configured on the key |
400 | mask_not_supported | mask isn't supported in this version |
400 | wrong_route_for_reference_image | Sent an image to /generations — use /edits |
400 | missing_image | /edits called without a file |
400 | too_many_reference_images | More than 4 references |
400 | unsupported_image_format | A reference isn't PNG, JPEG or WEBP |
403 | — | The key isn't from a stack on the image plan |
404 | model_not_found | model other than the served one (/edits only) |
413 | image_too_large | A reference over 5 MiB, or a body over the route's cap |
429 | queue_full | Already 3 in flight — respect Retry-After |
429 | — | Over 10 submissions per minute — respect Retry-After |
502 | — | The image was generated but couldn't be stored |
503 | model_not_ready | Infrastructure still starting, or degraded |
504 | queue_timeout | Waited more than 60s for a free slot |
Retry 429, 503 and 504 after the delay in Retry-After. A 502 is also
worth one retry, keeping in mind it renders again from scratch.

