Image Generation

POST /v1/images/generations

OpenAI-compatible image generation. The primary model is gpt-image-2. Two modes are supported:

  • Synchronous — the request blocks until the image is ready and returns it directly (best for low concurrency / interactive use).
  • Asynchronous — submit and immediately receive a job ID, then poll for the result (best for high volume / avoiding long-held connections).

Available models

Model Resolution tiers Notes
gpt-image-2 1K / 2K / 4K Primary model; general-purpose generation and editing.
gpt-image-2.5 1K 1K tier only.
gpt-image-2.5-flare 1K / 2K / 4K Everyday generation, faster turnaround.
gpt-image-2.5-sunburst 1K / 2K / 4K Precision editing; follows edit instructions closely.

Every model above supports both usage modes:

  • Text-to-imagePOST /v1/images/generations (synchronous) or POST /v1/images/generations/async (asynchronous).
  • Image-to-imagePOST /v1/images/edits (synchronous) or POST /v1/images/edits/async (asynchronous), sending image and prompt as multipart form fields. The size / resolution parameters are exactly the same as for text-to-image.

Compatibility: the model names gpt-image-2-2K and gpt-image-2-4K are still accepted so existing integrations keep working; they are equivalent to gpt-image-2 with resolution set to 2K / 4K. New integrations should use gpt-image-2 plus parameters.

Choosing the resolution tier

gpt-image-2 supports three resolution tiers — 1K / 2K / 4K — under one model name, selected by parameters (no need to switch models). Two equivalent ways:

  • resolution (recommended): set the tier directly to 1K / 2K / 4K; set the aspect ratio with size, or omit it (defaults to a 1:1 square).
  • size: pass the pixel dimensions for that tier (see the table below); the gateway derives both the tier and the aspect ratio from it.
Goal Request body
1K (default) omit the size parameters, or "resolution": "1K"
2K square "resolution": "2K", or "size": "2048x2048"
2K 16:9 landscape "resolution": "2K", "size": "2560x1440", or just "size": "2560x1440"
4K square "resolution": "4K", or "size": "2880x2880"
4K 16:9 landscape "resolution": "4K", "size": "3840x2160", or just "size": "3840x2160"

gpt-image-2.5-flare / gpt-image-2.5-sunburst work the same way; gpt-image-2.5 supports 1K only.

Synchronous: POST /v1/images/generations

Request parameters

Parameter Type Required Description
model string Yes Model ID, e.g. gpt-image-2.
prompt string Yes Text prompt describing the image.
n integer No Number of images. Only 1 is currently supported (default 1).
size string No Image size in WxH form, e.g. 1024x1024, 2560x1440, 3840x2160, or auto. It selects both the aspect ratio and the resolution tier (see the tier table below). A size that is not in the tier table is classified by pixel area: ≤ 2,000,000 → 1K; ≤ 5,000,000 → 2K; ≤ 9,000,000 → 4K; anything larger returns 400.
resolution string No Resolution tier: 1K / 2K / 4K (case-insensitive; image_size is accepted as an alias). Takes precedence over the tier implied by size; combine it with size to fix the aspect ratio while choosing the tier separately. If it names a tier the model does not support, the request returns 400.
quality string No Quality level. For the gpt-image-2.5 family: low / medium / high / xhigh / max / auto. For gpt-image-2 it is optional and does not currently change the output. This parameter does not affect the resolution tier.
response_format string No Output form: b64_json (default — the image inline as base64 in the JSON) or url (a download link valid for ~2 hours — see "Response" below). Any other value returns 400.

Tier precedence: resolution > the tier implied by size > default 1K.

Tip: for large images (2K/4K) or high concurrency, send "response_format": "url" — the response body is a few hundred bytes and the client downloads the image only when needed.

Resolution tier table

Aspect ratio 1K 2K 4K
1:1 1024×1024 2048×2048 2880×2880
16:9 1280×720 2560×1440 3840×2160
9:16 720×1280 1440×2560 2160×3840
3:2 1248×832 2496×1664 3504×2336
2:3 832×1248 1664×2496 2336×3504
4:3 1152×864 2304×1728 3264×2448
3:4 864×1152 1728×2304 2448×3264
5:4 1120×896 2240×1792 3200×2560
4:5 896×1120 1792×2240 2560×3200
21:9 1456×624 3024×1296 3696×1584

The model renders at its native resolution for the chosen tier and aspect ratio, so the pixel dimensions of the returned image can differ slightly from the requested size.

Billing

The billing unit and unit price for image generation are defined by the "Pricing" page and the Portal pricing page of the site you use. The usage block in the response reflects the token usage of this generation and can be used for reconciliation; higher tiers produce larger images and therefore cost more.

Examples

cURL

curl https://api.aiin1.ai/v1/images/generations \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "model": "gpt-image-2",
    "prompt": "a red fox running in snow, studio lighting",
    "n": 1
  }'

cURL (16:9 at the 2K tier, selected with size)

curl https://api.aiin1.ai/v1/images/generations \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "model": "gpt-image-2",
    "prompt": "a red fox running in snow, studio lighting",
    "size": "2560x1440",
    "n": 1
  }'

cURL (tier selected with resolution)

curl https://api.aiin1.ai/v1/images/generations \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "model": "gpt-image-2.5-flare",
    "prompt": "a red fox running in snow, studio lighting",
    "size": "1024x1024",
    "resolution": "2K",
    "n": 1
  }'

Python

import openai
 
client = openai.OpenAI(
    api_key="YOUR_API_KEY",
    base_url="https://api.aiin1.ai/v1",
)
 
resp = client.images.generate(
    model="gpt-image-2",
    prompt="a red fox running in snow, studio lighting",
    n=1,
)
b64 = resp.data[0].b64_json   # base64-encoded PNG

The OpenAI SDK has no native resolution field — pass it via extra_body:

resp = client.images.generate(
    model="gpt-image-2",
    prompt="a red fox running in snow, studio lighting",
    size="1024x1024",
    n=1,
    extra_body={"resolution": "2K"},
)

Image-to-image: POST /v1/images/edits

Upload the source image and the prompt as multipart/form-data. The response has the same shape as synchronous text-to-image (inline base64).

Parameter Type Required Description
image file Yes Input image — PNG / JPEG / WEBP, at most 25 MB each and 32 MB for the whole request. For multiple reference images repeat image[] (or image), up to 8 images.
prompt string Yes The edit instruction — what to change.
model string Yes Model ID, e.g. gpt-image-2 or gpt-image-2.5-sunburst (best at following edit instructions).
n integer No Number of images. Only 1 is currently supported.
size / resolution / quality string No Same meaning as for text-to-image — see the parameter table and tier table above.

The mask parameter is not supported yet and returns an error if sent; describe the region to change in prompt instead.

An edit usually takes 1–3 minutes and can take longer at peak times. Set the client read timeout to at least 15 minutes, otherwise the client may disconnect before the image is ready — or use the asynchronous edit route described at the end of this page.

Reference images can also be passed as image URLs or data URLs (JSON form, no file upload), on both the synchronous and asynchronous routes — see "Reference images by URL" at the end of this page.

cURL (single image)

curl https://api.aiin1.ai/v1/images/edits \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F image=@input.png \
  -F model=gpt-image-2.5-sunburst \
  -F prompt="turn the sky into a starry night, keep the fox unchanged" \
  -F resolution=2K

cURL (multiple reference images, 16:9 landscape)

curl https://api.aiin1.ai/v1/images/edits \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "image[]=@product.png" \
  -F "image[]=@background.jpg" \
  -F model=gpt-image-2 \
  -F prompt="place the product from the first image onto the scene in the second image" \
  -F size=2560x1440

Python

import base64
import openai
 
client = openai.OpenAI(
    api_key="YOUR_API_KEY",
    base_url="https://api.aiin1.ai/v1",
    timeout=900,   # edits are slow — keep the read timeout at 15 minutes or more
)
 
with open("input.png", "rb") as f:
    resp = client.images.edit(
        model="gpt-image-2.5-sunburst",
        image=f,                      # multiple reference images: image=[f1, f2]
        prompt="turn the sky into a starry night, keep the fox unchanged",
        extra_body={"resolution": "2K"},
    )
 
with open("output.png", "wb") as out:
    out.write(base64.b64decode(resp.data[0].b64_json))

Response

The image is returned inline as base64:

{
  "created": 1730000000,
  "data": [
    {
      "b64_json": "iVBORw0KGgoAAAANSUhEUgAA...",
      "revised_prompt": "..."
    }
  ],
  "usage": {
    "input_tokens": 18,
    "input_tokens_details": {
      "image_tokens": 0,
      "text_tokens": 18
    },
    "output_tokens": 7024,
    "output_tokens_details": {
      "image_tokens": 7024,
      "text_tokens": 0
    },
    "total_tokens": 7042
  }
}

To display in a browser, prefix it: data:image/png;base64,<b64_json>.

With "response_format": "url" the response carries a download link instead of base64:

{
  "created": 1730000000,
  "data": [
    {
      "url": "https://<bucket>.r2.cloudflarestorage.com/...&X-Amz-Signature=...",
      "revised_prompt": "..."
    }
  ],
  "usage": { "...": "same as above" }
}

The link is valid for about 2 hours — download or re-host it within that window; the image is deleted after 48 hours. In the rare case the image was generated but the link could not be created, the request returns 502 and is not billed; simply retry.


Asynchronous: POST /v1/images/generations/async

The request body is identical to synchronous text-to-image (model / prompt / n / size / resolution / quality, with the same meanings as above), but it returns a job ID immediately instead of blocking. Use this for batch generation or when the client shouldn't hold a long connection open.

This section covers async text-to-image. For async image-to-image see "Asynchronous image-to-image" at the end of this page.

1. Submit

curl https://api.aiin1.ai/v1/images/generations/async \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "model": "gpt-image-2",
    "prompt": "a red fox running in snow",
    "size": "2560x1440",
    "n": 1
  }'

Returns 202 Accepted:

{
  "id": "nximg_xxxxxxxx",
  "status": "queued",
  "created": 1730000000,
  "object": "image.generation.async"
}

2. Poll: GET /v1/images/generations/async/{job_id}

curl https://api.aiin1.ai/v1/images/generations/async/nximg_xxxxxxxx \
  -H "Authorization: Bearer YOUR_API_KEY"

status transitions: queuedprocessingsucceeded (or failed). Poll every 1–2 seconds; generation usually takes seconds to tens of seconds.

In progress:

{ "id": "nximg_xxxxxxxx", "status": "processing", "created": 1730000000 }

Succeeded:

{
  "id": "nximg_xxxxxxxx",
  "status": "succeeded",
  "created": 1730000000,
  "data": [
    {
      "url": "https://<bucket>.r2.cloudflarestorage.com/...&X-Amz-Signature=...",
      "revised_prompt": "..."
    }
  ],
  "usage": {
    "input_tokens": 18,
    "input_tokens_details": {
      "image_tokens": 0,
      "text_tokens": 18
    },
    "output_tokens": 7024,
    "output_tokens_details": {
      "image_tokens": 7024,
      "text_tokens": 0
    },
    "total_tokens": 7042
  }
}

Failed:

{
  "id": "nximg_xxxxxxxx",
  "status": "failed",
  "created": 1730000000,
  "error": { "type": "upstream_error", "message": "generation failed", "code": "..." }
}

About url: unlike the synchronous route (inline base64), the async route returns a signed download URL on success (valid for ~2 hours). Download or re-host it within the validity window; an expired URL requires regenerating. Failed jobs are not billed.

Field reference

Field Description
id Job ID (nximg_ prefix), used for polling. Bound to your API key's organization — others cannot access it.
status queued / processing / succeeded / failed.
data[].url Signed image download URL on success (~2h validity).
data[].revised_prompt Model-revised prompt (may be empty).
usage Returned on success, in the same shape as the synchronous response.

Asynchronous image-to-image: POST /v1/images/edits/async

The asynchronous version of POST /v1/images/edits. The request is exactly the same (multipart/form-data file upload, or the JSON form with image URLs; parameters as in the "Image-to-image" section above), but it returns a job ID immediately; poll for the result once the image is ready. Edits are slow (usually 1–3 minutes), so this route is recommended for batch work or when the client shouldn't hold a connection open.

Differences from the synchronous edit route:

  • Up to 8 input images, at most 25 MB each and 32 MB for the whole request.
  • mask is not supported and returns 400.
  • On success it returns a signed download URL (valid for ~2 hours), not inline base64.

1. Submit a job

curl https://api.aiin1.ai/v1/images/edits/async \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F image=@input.png \
  -F model=gpt-image-2 \
  -F prompt="turn the sky into a starry night, keep the fox unchanged" \
  -F resolution=2K

For multiple reference images repeat image[] (or image), as with the synchronous route.

Returns 202 Accepted:

{
  "id": "nximg_xxxxxxxx",
  "status": "queued",
  "created": 1730000000,
  "object": "image.edit.async"
}

2. Poll for the result: GET /v1/images/edits/async/{job_id}

curl https://api.aiin1.ai/v1/images/edits/async/nximg_xxxxxxxx \
  -H "Authorization: Bearer YOUR_API_KEY"

The response is exactly the same as for async text-to-image (queuedprocessingsucceeded / failed; on success data[].url is the download link and usage is included). Poll every 2–5 seconds. Job IDs are shared by both async routes, so GET /v1/images/generations/async/{job_id} also works.

Python example

import time
import requests
 
API = "https://api.aiin1.ai/v1"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}
 
# 1. Submit (multiple reference images: files=[("image[]", f1), ("image[]", f2)])
with open("input.png", "rb") as f:
    job = requests.post(
        f"{API}/images/edits/async",
        headers=HEADERS,
        files=[("image", ("input.png", f, "image/png"))],
        data={
            "model": "gpt-image-2",
            "prompt": "turn the sky into a starry night, keep the fox unchanged",
            "resolution": "2K",
        },
        timeout=120,
    ).json()
 
# 2. Poll
while True:
    r = requests.get(f"{API}/images/edits/async/{job['id']}", headers=HEADERS, timeout=30).json()
    if r["status"] in ("succeeded", "failed"):
        break
    time.sleep(3)
 
if r["status"] == "succeeded":
    img = requests.get(r["data"][0]["url"], timeout=120).content   # URL valid for ~2 hours
    open("output.png", "wb").write(img)
else:
    print(r["error"])

Reference images by URL (JSON form)

Applies to POST /v1/images/edits (synchronous) and POST /v1/images/edits/async (asynchronous). When your images already live in object storage, a CDN or an image host, there is no need to download and re-upload them — put the links straight into a JSON request body.

How it works: the gateway first downloads and validates every reference image, and only starts generating once all of them pass. If any image has a problem, the request returns 400 before generation and is not billed. This prevents the case where a broken link still produces an image unrelated to your reference — and you get charged for it.

Request format

  • Header: Content-Type: application/json (cannot be combined with multipart; a request uses one form or the other).
  • Body parameters:
Parameter Type Required Description
model string Yes Model ID, e.g. gpt-image-2.
prompt string Yes The edit instruction. With several reference images you can refer to "the first image", "the second image", in the same order as the images array.
images array Yes Reference images, up to 8; see the table below for how each item can be written.
size / resolution / quality string No Same meaning as for text-to-image — see the parameter table and tier table above.
response_format string No b64_json (default) or url. Synchronous route only; the asynchronous route always returns a link.
n integer No Only 1 is currently supported.

mask is not supported and returns 400.

How each item in images can be written

All of the following are accepted, and they can be mixed within one array:

Form Example
Image URL (string) "https://cdn.example.com/product.png"
data URL (string) "data:image/png;base64,iVBORw0KGgo..."
OpenAI-style object {"image_url": "https://cdn.example.com/product.png"}
OpenAI-style nested object {"image_url": {"url": "https://cdn.example.com/product.png"}}

You can also use the image field instead of images; its value may be a single string or an array.

URL requirements

Item Requirement
Scheme and port https:// only, on the default port 443; http:// and explicit port numbers are not supported.
Address Must be a domain name reachable directly from the public internet — not an IP address, localhost or an internal address.
Access The gateway downloads without any cookies, login session or custom headers. Images that require a login or are hotlink-protected (Referer checks) will fail; for private objects, provide a signed temporary URL (e.g. an S3 / OSS / COS / R2 presigned URL).
Redirects Redirects are not followed. Short links and share links that redirect are rejected — pass the final image URL.
Format PNG / JPEG / WEBP only. The format is detected from the file content, not from the URL extension or the server's Content-Type; GIF, HEIC, SVG and BMP are rejected.
Size At most 25 MB per image and 64 MB for all reference images together.
Download timeout 5 seconds to connect and 30 seconds in total per image; a timeout is an error.
Signed-URL validity Synchronous route: the link only has to be valid when the request is sent. Asynchronous route: reference images are downloaded at submit time, so the link only has to be valid when you submit — it does not need to cover queueing and generation.

Tip: compress reference images before sending them, ideally to 2 MB or less each (for example a JPEG whose longer side is at most 2048 pixels). Smaller images are submitted faster and succeed more often, especially at peak times.

data URL requirements

  • The form must be data:image/png;base64,..., data:image/jpeg;base64,... or data:image/webp;base64,....
  • The base64 must be valid, and the declared type must match the actual content (declaring image/png for JPEG content is rejected).
  • data URLs count toward the 32 MB whole-request limit, and base64 adds about a third, so keep each original image under roughly 20 MB; prefer links for large images.

Converting a local file to a data URL:

import base64
 
def to_data_url(path: str) -> str:
    data = open(path, "rb").read()
    if data.startswith(b"\x89PNG"):
        mime = "image/png"
    elif data.startswith(b"\xff\xd8\xff"):
        mime = "image/jpeg"
    elif data[:4] == b"RIFF" and data[8:12] == b"WEBP":
        mime = "image/webp"
    else:
        raise ValueError("only PNG / JPEG / WEBP are supported")
    return f"data:{mime};base64,{base64.b64encode(data).decode()}"

Examples

cURL: synchronous, two reference images (one URL, one data URL), link output

curl https://api.aiin1.ai/v1/images/edits \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -d '{
    "model": "gpt-image-2",
    "prompt": "place the product from the first image into the scene of the second image, keeping the product unchanged",
    "images": [
      "https://cdn.example.com/product.png",
      "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ..."
    ],
    "size": "2560x1440",
    "response_format": "url"
  }'

Python: synchronous

import requests
 
API = "https://api.aiin1.ai/v1"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}
 
r = requests.post(
    f"{API}/images/edits",
    headers=HEADERS,
    json={
        "model": "gpt-image-2",
        "prompt": "turn the sky into a starry night, keep everything else unchanged",
        "images": ["https://cdn.example.com/photo.jpg"],
        "response_format": "url",
    },
    timeout=900,   # edits are slow — keep the read timeout at 15 minutes or more
)
d = r.json()
if r.status_code != 200:
    raise RuntimeError(d["error"]["message"])      # e.g. "images[0]: host cdn.example.com answered HTTP 404"
img = requests.get(d["data"][0]["url"], timeout=120).content   # link valid for ~2 hours
open("output.png", "wb").write(img)

Python: asynchronous

import time
import requests
 
API = "https://api.aiin1.ai/v1"
HEADERS = {"Authorization": "Bearer YOUR_API_KEY"}
 
r = requests.post(
    f"{API}/images/edits/async",
    headers=HEADERS,
    json={
        "model": "gpt-image-2",
        "prompt": "turn the sky into a starry night, keep everything else unchanged",
        "images": ["https://cdn.example.com/photo.jpg"],
    },
    timeout=120,
)
job = r.json()
if r.status_code != 202:                       # a bad reference image is rejected with 400 at submit time
    raise RuntimeError(job["error"]["message"])
 
while True:
    res = requests.get(f"{API}/images/edits/async/{job['id']}", headers=HEADERS, timeout=30).json()
    if res["status"] in ("succeeded", "failed"):
        break
    time.sleep(3)

Error reference

A problem with a reference image returns HTTP 400 with error.type = invalid_request_error. For an error about one particular image, message starts with images[index]: (0-based, the position in the images array) followed by the reason; errors about the request as a whole (too many images, mask sent, no image given) have no index prefix. Errors show the host only, never the full URL.

Reason in message Meaning What to do
only https:// URLs are accepted The link is not https Use an https link
only the default https port 443 is allowed The link contains a port number Remove the port and use a standard https address
URL host must be a domain name, not an IP The link uses an IP address Use a domain name
URL host is not a public hostname / resolves to a non-public address localhost, an internal name, or a domain that resolves to an internal address Use a publicly reachable address
could not resolve host ... The domain cannot be resolved Check the domain name
answered with a redirect (3xx) The link redirects Use the final address after the redirect
answered HTTP 403 / answered HTTP 404, etc. Access denied or file not found (typically hotlink protection, login required, or an expired signed link) Make sure the link opens directly and anonymously; for private files use an unexpired presigned link
timed out fetching from host ... / could not fetch from host ... Download timed out or the connection failed Make sure the server is up and fast enough, or use a data URL instead
content is not a PNG, JPEG or WebP image The downloaded content is not a supported image (e.g. an HTML page, GIF or HEIC) Make sure the link points at the image file itself and convert it to PNG / JPEG / WEBP
image larger than ... bytes / images exceed ... bytes in total Over 25 MB for one image or over 64 MB in total Compress the images or send fewer
data URL must be data:image/(png|jpeg|webp);base64, / data URL is not valid base64 / data URL content is not a valid image/... Wrong data URL form, invalid base64, or the declared type does not match the content Build it as described under "data URL requirements" above
too many images (max 8) More than 8 reference images Send 8 or fewer
mask is not supported with a JSON body mask was sent Remove mask and describe the area to change in prompt
images is required (a list of image URLs or data URLs) No image given, or images is an empty array Send at least one reference image

Billing

  • A reference image fails validation: no image is generated and nothing is billed.
  • Validation passes and generation succeeds: billed exactly the same as the file-upload form.