Image Generation
Generate images through the Zora public API with gpt-img-2. Each accepted request creates one image. Use async image jobs when the client wants a quick submit response and a polling URL; use the OpenAI-compatible route only when the client is willing to wait for one image.
One image per request
Zora no longer accepts server-side image batches.
n must be
1. To generate 50 images, submit 50 independent one-image jobs and poll each job separately. Render each image as soon as its own job returns
completed; do not wait for the other jobs.
Image-only plan limits
Image-only keys use a rolling RPM window. A
15 RPM image key can submit up to 15 one-image requests immediately; each used slot becomes available again 60 seconds after it was accepted. If 1 image was accepted less than 60 seconds ago, only 14 more image requests can be accepted until that slot refills.
No over-RPM queue
The server does not queue image requests that exceed the key's RPM. Requests over the rolling window return HTTP
429 with
Retry-After. Submit again after the indicated delay.
Endpoints
POST https://api.zora.io.vn/v1/images/jobs
GET https://api.zora.io.vn/v1/images/jobs/{job_id}
POST https://api.zora.io.vn/v1/images/generations # n=1 only
Async Image Job
This is the recommended production path. Submit returns quickly with 202, then the client polls the returned status URL until the one image reaches a terminal state.
curl https://api.zora.io.vn/v1/images/jobs \
-H "Authorization: Bearer YOUR_ZORA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: customer-image-20260619-001" \
-d '{
"model": "gpt-img-2",
"prompt": "Create a funny motion sticker",
"n": 1,
"size": "1024x1024",
"response_format": "url",
"upload_to_storage": true
}'
{
"id": "imgjob_...",
"status": "queued",
"status_url": "/v1/images/jobs/imgjob_...",
"poll_after_seconds": 5,
"requested": 1,
"succeeded": 0,
"failed": 0,
"pending": 1
}
curl https://api.zora.io.vn/v1/images/jobs/imgjob_... \
-H "Authorization: Bearer YOUR_ZORA_API_KEY"
{
"id": "imgjob_...",
"status": "generating",
"requested": 1,
"succeeded": 0,
"failed": 0,
"pending": 1,
"progress": {
"completed": 0,
"total": 1,
"percent": 0
},
"data": [],
"items": [
{
"index": 1,
"status": "running",
"attempts": 1
}
],
"poll_after_seconds": 5
}
Recommended loop: submit one image request per desired output, poll each accepted job independently every 5-10 seconds, and respect Retry-After on both job polling and HTTP429 rate limit responses.
Progressive rendering
Client applications should update their gallery from each job's latest
data or completed
items on every poll. Each job tracks exactly one image, so a slow retry only delays that one job.
Different Prompts
Submit one job per desired image. items is accepted only with one item, so it is mainly a compatibility shape for clients that already build item payloads.
curl https://api.zora.io.vn/v1/images/jobs \
-H "Authorization: Bearer YOUR_ZORA_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: gallery-item-001" \
-d '{
"model": "gpt-img-2",
"size": "1152x2048",
"response_format": "url",
"upload_to_storage": true,
"items": [
{
"prompt": "other angle",
"image_url": "https://example.com/input-1.jpg"
}
]
}'
Synchronous One-Off
Use this route only for convenience calls where the client is willing to wait for one image. Requests with n higher than 1 are rejected.
curl https://api.zora.io.vn/v1/images/generations \
-H "Authorization: Bearer YOUR_ZORA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-img-2",
"prompt": "A clean product photo of a ceramic coffee cup",
"n": 1,
"size": "1024x1024",
"response_format": "url",
"upload_to_storage": true
}'
Successful URL responses are normalized to Zora-hosted public download links underhttps://api.zora.io.vn/v1/images/files/.... The files are backed by Cloudflare R2 when storage is available, with a local Zora cache as fallback.
Response
{
"created": 1780656950,
"data": [
{
"url": "https://api.zora.io.vn/v1/images/files/generated-1781867780-ecbcabe4d092.png",
"raw_url": "https://api.zora.io.vn/v1/images/files/generated-1781867780-ecbcabe4d092.png",
"filename": "generated-1781867780-ecbcabe4d092.png",
"source": "cloudflare_r2"
}
],
"requested": 1,
"succeeded": 1,
"failed": 0
}
Image To Image
Send a public HTTPS image URL or a data:image/...;base64,... data URI through image_url, input_image, or image. Each input image can be up to 10 MB. Very large phone photos are automatically resized when they exceed 20 megapixels. Use size: "auto" when you want Zora to preserve or infer the canvas/aspect ratio.
curl https://api.zora.io.vn/v1/images/generations \
-H "Authorization: Bearer YOUR_ZORA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-img-2",
"prompt": "Turn this product photo into a premium studio ad, keep the object shape",
"image_url": "https://example.com/reference.png",
"n": 1,
"size": "auto",
"response_format": "url",
"upload_to_storage": true
}'
Multiple Reference Images
To send several uploaded images, pass an array in image_url, input_image, or image. The current limit is 4 reference images per request.
{
"model": "gpt-img-2",
"prompt": "Design a banner using the logo and product reference",
"image_url": [
"https://example.com/logo.png",
"https://example.com/product-photo.jpg"
],
"n": 1,
"size": "auto",
"response_format": "url",
"upload_to_storage": true
}
Request Fields
| Field | Value |
|---|
model | gpt-img-2 |
prompt | Required text instruction. |
n | 1 only. Each accepted request costs 1 image and 1 rolling-RPM slot. |
size | 1024x1024 by default. Use a fixed value such as 1024x1024, 1024x1536, or 1536x1024 when outputs must have consistent pixel dimensions. Use auto only when variable canvas/aspect ratio is desired. |
response_format | url or b64_json. |
upload_to_storage | Compatibility flag. URL responses are returned as Zora-hosted public files when possible. |
image_url | Optional string or array of up to 4 public HTTPS URLs or base64 data URIs. |
items | Optional array with exactly 1 per-image payload. More than 1 item returns HTTP 400. |
Operational Notes
- Image-only keys are scoped to image routes; do not smoke test them with
GET /v1/models. - Use
/v1/images/jobs for UIs that want quick submit responses and polling. - Submit one image per request. Requests above rolling RPM return HTTP
429; they are not queued. - Render completed job items immediately.
- Poll each active job using the response
Retry-After header or poll_after_seconds. Do not poll every card every second. - For uniform gallery cards, send an explicit fixed
size. Fixed-size outputs are normalized to that canvas; size: "auto" can return mixed dimensions. - Use
/v1/images/generations only for one-off n: 1 convenience calls. - The daily bucket is UTC. There is no fixed monthly total cap beyond the active 30-day validity and daily quota.
- If an image job fails, retry it with a new idempotency key after checking the current RPM window.