Image generation
Submit a prompt, receive a job ID, poll until the images are ready.
This endpoint is asynchronous
Unlike chat, it does not return an image. It returns 202 Accepted with a job ID that you poll. A single image takes tens of seconds to a few minutes — far longer than any proxy will hold a connection open, which is exactly why the API is shaped this way.
Create a job#
/v1/images/generationscurl https://api.xkiro.com/v1/images/generations \
-H "Authorization: Bearer $XKIRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-image",
"prompt": "A lighthouse on a rocky shore at dawn, long exposure",
"n": 1,
"size": "1024x1024"
}'{
"id": "f32a1796-15f4-43ba-8098-22d7dc2f66c1",
"object": "image.generation.job",
"status": "processing",
"created": 1785734400,
"model": "gpt-image",
"prompt": "A lighthouse on a rocky shore at dawn, long exposure",
"aspect_ratio": "1:1",
"style": null
}| Field | Type | Description |
|---|---|---|
promptrequired | string | What to draw. Detail helps: subject, setting, lighting, style, framing. |
model | string | Image model ID. Omit to use your account default. |
n | integer | How many images to return. Each one counts as a separate billable unit. |
size | string | Pixel size such as 1024x1024 or 1792x1024. Converted to the nearest aspect ratio the model supports. |
style | string | Optional style hint passed through to the model. |
source_job_id | string | Re-render an existing image at a different aspect ratio. Combine with size. See Change aspect ratio. |
Poll for the result#
/v1/images/generations/{id}curl https://api.xkiro.com/v1/images/generations/f32a1796-15f4-43ba-8098-22d7dc2f66c1 \
-H "Authorization: Bearer $XKIRO_API_KEY"{
"id": "f32a1796-15f4-43ba-8098-22d7dc2f66c1",
"object": "image.generation.job",
"status": "succeeded",
"created": 1785734400,
"model": "gpt-image",
"prompt": "A lighthouse on a rocky shore at dawn, long exposure",
"aspect_ratio": "1:1",
"data": [
{ "url": "https://cdn.xkiro.com/images/01JQZ8K3M7N2P4R6S8T0V2W4X6-0.png" }
]
}Job status
| Field | Type | Description |
|---|---|---|
processing | string | Still running. Keep polling. |
succeeded | string | Done. data[].url holds CDN links. |
failed | string | Something went wrong. error explains what. Not billed. |
blocked | string | The provider refused the prompt on content grounds. Retrying the same prompt will not help; reword it. |
async function generateImage(prompt: string) {
const created = await fetch("https://api.xkiro.com/v1/images/generations", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.XKIRO_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ prompt, n: 1, size: "1024x1024" }),
}).then((r) => r.json());
// Give up eventually. A job that never leaves "processing" would otherwise
// poll forever and hold the caller open.
const deadline = Date.now() + 5 * 60_000;
let waitMs = 2_000;
while (Date.now() < deadline) {
await new Promise((r) => setTimeout(r, waitMs));
waitMs = Math.min(waitMs * 1.5, 10_000);
const job = await fetch(
`https://api.xkiro.com/v1/images/generations/${created.id}`,
{ headers: { Authorization: `Bearer ${process.env.XKIRO_API_KEY}` } },
).then((r) => r.json());
if (job.status === "succeeded") return job.data.map((d: { url: string }) => d.url);
if (job.status === "failed" || job.status === "blocked") {
throw new Error(job.error?.message ?? job.status);
}
}
throw new Error("Image generation timed out");
}Poll every few seconds, not every few hundred milliseconds
Nothing changes faster than that, and aggressive polling counts against your rate limit — it can get you throttled while you wait for your own image.
List your jobs#
/v1/images/generationsReturns recent jobs newest first, so a gallery survives a page reload without any client-side storage. Paginate with before, using next_before from the previous page.
{
"object": "list",
"data": [ /* job objects, newest first */ ],
"next_before": "2026-08-03T15:12:01.094Z"
}Change aspect ratio#
Pass source_job_id together with a new size to re-render an existing image at a different shape. This creates a new job and costs one unit; the original is untouched.
curl https://api.xkiro.com/v1/images/generations \
-H "Authorization: Bearer $XKIRO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source_job_id": "f32a1796-15f4-43ba-8098-22d7dc2f66c1",
"size": "1792x1024"
}'Billing and limits#
- One image is one billable unit, regardless of size. Requesting
n: 4costs four units. - Every plan includes a number of free images per rolling 24 hours. Past that, images are charged to your wallet — they never consume your plan's spending window.
- Failed and cancelled jobs do not count against your allowance. Jobs refused on content grounds do, because the attempt consumed upstream capacity.
- There is a cap on how many jobs you can have in flight at once, so one account cannot occupy the whole queue. Submit the next batch as earlier jobs finish.
Writing prompts that work#
- Name the subject, the setting, the lighting and the framing. "A lighthouse" is a coin flip; "a white lighthouse on wet black rocks at dawn, low mist, wide shot" is a photograph.
- Describe what you want, not what you do not. Negations are unreliable across image models.
- Keep the prompt with the image. The job object returns
prompt,aspect_ratioandstyleso you can rebuild a gallery without your own database.
To edit an image you already have, see Image editing.
