Skip to content
Sign Up Free
GPT Image

Image Generation API

Generate GPT Image results with BetterToken's OpenAI-compatible API. Set API Key, Base URL, and parameters; use SDK examples and check billing and errors.

POST /v1/images/generations

The text-to-image endpoint uses an application/json request body. Submit a prompt, keep the HTTP request open, and read each generated image from data[].b64_json in the same response.

Choose the right workflow

  • Generate an image from text and save the result: stay on this page. Start with the curl example, then use Save the image to decode each entry in data[].
  • Edit an existing image: go to the image editing API and upload one or more reference images. Add a mask only when you want to guide edits to a local area.
  • Choose a model, review parameters, and estimate budget: check current Model IDs and prices in the model plaza. The GPT Image 2 guide covers a complete workflow.

Quick start with curl

Replace YOUR_API_KEY with your BetterToken API Key. One key works with all available models. This request saves the full response to response.json for the Python example below:

Terminal window
curl --max-time 300 -X POST "https://bettertoken.ai/v1/images/generations" \
-o response.json \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-image-2",
"prompt": "A product photo of a green ceramic mug on a studio table",
"size": "1024x1024",
"n": 1,
"response_format": "b64_json",
"output_format": "png"
}'

Start with these values. n=1 is recommended; the endpoint accepts an integer from 1 to 10, but can return fewer images than requested.

{
"model": "gpt-image-2",
"n": 1,
"response_format": "b64_json",
"output_format": "png"
}
ParameterWhat to sendPurpose
modelgpt-image-2, gpt-image-2.5-flare, or gpt-image-2.5-sunburstSelect a Model ID shown as available in the model plaza. A valid ID does not guarantee that a model, size, or operation is currently available to your request.
promptA text descriptionRequired image description. Include the subject, style, composition, and important constraints.
sizeOmit it, use auto, or provide dimensions such as 1024x1024Requests an output size and aspect ratio. Returned dimensions can differ.
nInteger from 1 to 10Maximum requested image count. 1 is a practical starting point; the response may contain fewer images.
qualityauto, low, medium, or high for gpt-image-2; xhigh and max are also accepted by the two 2.5 modelsOptional quality preference. Omit it or use JSON null for the default. Acceptance does not guarantee availability or that a higher setting improves image quality.
response_formatb64_jsonReturns each image as base64 in data[].b64_json.
output_formatFor example, pngRequests an output format; it does not guarantee the returned file is encoded in that format. Detect the actual image format from its bytes before saving.

The response may contain fewer images than n. Process every item in data[]; a recommendation to use n=1 is not a server limit.

Accepted parameters do not guarantee that the selected model, size, or operation is available for every request. Check current availability in the model plaza.

sizeRatioUse case
autoAutoAutomatic size selection
1024x10241:1Square images, avatars, covers, assets
1536x10243:2Landscape posters, banners, scenes
1024x15362:3Portrait mobile covers and posters
1536x11524:3Standard landscape images, product images, content graphics
1152x15363:4Standard portrait images, mobile covers, vertical posters
2048x20481:1High-resolution square images
2048x115216:9High-resolution landscape images
3840x216016:94K landscape images
2160x38409:164K portrait images

The table shows useful examples, not a closed list. You can omit size, set it to auto, or use JSON null for automatic sizing. Explicit dimensions must be positive multiples of 16, each no greater than 3840, have an aspect ratio no wider or taller than 3:1, and contain 655360 to 8294400 pixels. For example, 2080x2080 is valid. Requested dimensions describe intent; delivery dimensions can differ, so inspect the returned image rather than forcibly cropping it.

Save the image

A successful response follows the OpenAI-compatible image response shape. The amounts below illustrate the receipt format and are not fixed prices:

{
"created": 1710000000,
"data": [
{
"b64_json": "iVBORw0KGgoAAAANSUhEUgAA...(truncated)",
"revised_prompt": "Optional revised prompt"
}
],
"bettertoken_image_billing": {
"currency": "CNY",
"settled_amount": "0.15",
"outputs": [
{
"index": 0,
"width": 1254,
"height": 1254,
"billing_resolution": "2K",
"currency": "CNY",
"amount": "0.15"
}
]
}
}

Read every data[].b64_json and allow additional fields such as revised_prompt. BetterToken returns billing details in the root bettertoken_image_billing object. Its settled_amount and per-output amount are decimal strings; outputs[].index is zero-based. The delivered dimensions and billing_resolution in this object are authoritative for the completed request.

output_format is a request, not a guarantee or a conversion instruction. Inspect the returned bytes and use the matching file extension. The following standard-library code recognizes PNG, JPEG, and WebP without an added dependency.

import base64
import json
from pathlib import Path
result = json.loads(Path("response.json").read_text(encoding="utf-8"))
for index, item in enumerate(result["data"], start=1):
encoded = item["b64_json"]
if "," in encoded and "base64" in encoded.split(",", 1)[0]:
encoded = encoded.split(",", 1)[1]
image_bytes = base64.b64decode(encoded)
if image_bytes.startswith(b"\x89PNG\r\n\x1a\n"):
extension = "png"
elif image_bytes.startswith(b"\xff\xd8\xff"):
extension = "jpg"
elif image_bytes.startswith(b"RIFF") and image_bytes[8:12] == b"WEBP":
extension = "webp"
else:
raise ValueError("Unsupported image signature")
Path(f"output-{index}.{extension}").write_bytes(image_bytes)

JavaScript example (Node.js)

import { writeFile } from "node:fs/promises";
const response = await fetch("https://bettertoken.ai/v1/images/generations", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "gpt-image-2",
prompt: "A green ceramic mug on a studio table",
size: "1024x1024",
n: 1,
response_format: "b64_json",
output_format: "png",
}),
});
if (!response.ok) {
throw new Error(await response.text());
}
const result = await response.json();
const { data } = result;
for (const [index, item] of data.entries()) {
const b64Json = item.b64_json.replace(/^data:.*;base64,/, "");
const imageBytes = Buffer.from(b64Json, "base64");
let extension;
if (imageBytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
extension = "png";
} else if (imageBytes[0] === 0xff && imageBytes[1] === 0xd8 && imageBytes[2] === 0xff) {
extension = "jpg";
} else if (imageBytes.toString("ascii", 0, 4) === "RIFF" && imageBytes.toString("ascii", 8, 12) === "WEBP") {
extension = "webp";
} else {
throw new Error("Unsupported image signature");
}
await writeFile(`output-${index + 1}.${extension}`, imageBytes);
}

If you need a fixed PNG, JPEG, or WebP encoding, identify the actual format first and transcode as needed. Changing a file extension alone does not convert the image.

Read the actual cost

bettertoken_image_billing is returned with the generation result; you do not need a separate cost query.

FieldMeaning
currencyCurrency used for this settlement. Do not assume it is always USD or CNY.
settled_amountTotal amount settled for this request, represented as a decimal string.
requested_resolutionResolution tier inferred from the request.
outputs[].indexZero-based image index corresponding to data[].
outputs[].width, heightActual width and height of the corresponding image, in pixels.
outputs[].billing_resolutionFinal billing tier for the corresponding image.
outputs[].currency, amountSettlement currency and amount for the corresponding image.

The requested dimensions and billing tier can differ. For example, a request for 1024x1024 has returned 1254x1254 and been billed at 2K. Use the actual amount in the receipt; check current unit prices in the model plaza.

Use the following code after the Python save example. Keep amounts as strings for display; use decimal arithmetic for calculations. Do not record a missing receipt as zero.

from decimal import Decimal
billing = result.get("bettertoken_image_billing")
if billing is None:
print("Billing receipt unavailable; check your usage records.")
else:
total = Decimal(billing["settled_amount"])
print(f"Total: {billing['currency']} {total}")
for output in billing.get("outputs", []):
print(
f"Image {output['index'] + 1}: "
f"{output['width']}x{output['height']}, "
f"{output['billing_resolution']}, "
f"{output['currency']} {output['amount']}"
)

Response flow

This endpoint is synchronous. After sending POST /images/generations, keep the current HTTP request open until the server responds. On success, each image is returned in data[].b64_json with its delivery and billing details.

The endpoint does not return a task_id, and there is no separate status query or result download endpoint.

Timeouts, retries, and billing

Set HTTP client timeouts to several minutes. A timeout, disconnect, or ambiguous 5xx does not tell you whether generation ran or was billed. The connection can fail while the result is being written after settlement, so duplicate charges are possible. Do not automatically repeat a generation or edit POST. First check consumption records or request logs; retain any returned request ID and, if received, the X-BetterToken-Image-Receipt and X-BetterToken-Billing-State response headers. A client-generated request ID does not make the server idempotent; a deliberate resend can create a new request and charge. There is no separate status-query endpoint.

Fix rejected input, authentication, and quota errors before sending again. Retry a 429 only when it represents a confirmed rejection, using backoff. Do not treat every 5xx or transport error as safe to retry.

Before processing, BetterToken may preauthorize an upper bound equal to the highest applicable unit price multiplied by n. A balance sufficient for the delivered 1K image may still be insufficient for this hold; the request is rejected with 403 and error.code=insufficient_user_quota. The final charge is based on images actually delivered. The root bettertoken_image_billing object reports the final settled_amount, currency, each output’s amount and billing_resolution, and delivered dimensions. Amounts are decimal strings. The response can contain fewer images than n.

Error handling

Errors usually return JSON. When showing an error, read error.message first, then message, then the HTTP status text.

HTTP statusMeaningWhat to do
400Malformed JSON, a missing parameter, or an unsupported sizeCheck the request body. Do not retry automatically.
401API Key is missing or invalidCheck the Authorization: Bearer YOUR_API_KEY header.
402 or 403Insufficient quota or balance, including a balance holdCheck error.code and account quota/balance. 403 with error.code=insufficient_user_quota means the balance may be below the preauthorization upper bound.
429Rate limit, concurrency limit, or busy upstreamRetry only after confirming the request was rejected; use backoff.
5xxGateway or upstream failureExecution and billing may be unknown. Check request logs and consumption before considering a deliberate resend.

Integration checklist

  • The Base URL is https://bettertoken.ai/v1.
  • The header includes Authorization: Bearer sk-....
  • The request uses application/json.
  • Choose a Model ID available for this request: gpt-image-2, gpt-image-2.5-flare, or gpt-image-2.5-sunburst.
  • Use response_format=b64_json. output_format is a request value; identify the actual format from the bytes before saving.
  • n accepts integers from 1 to 10. Start with 1; fewer images may be returned.
  • Omit size, use auto, or provide valid dimensions. The table above is not a closed list.
  • The HTTP client allows several minutes for generation.
  • Logs and error screenshots do not expose the API Key or full base64 image contents.
POST/v1/images/generations

Authorizations

Authorizationstringheaderrequired

Bearer authentication header of the form Bearer <token>, where <token> is your auth token.

Body application/json

modelenum<string>default: gpt-image-2required

GPT Image Model ID. One BetterToken API Key works with all available models.

Available options: gpt-image-2, gpt-image-2.5-flare, gpt-image-2.5-sunburst

Example:"gpt-image-2"
promptstringrequired

A detailed description of the image to generate.

Example:"A futuristic AI product poster on a light background, with glass textures, a clean composition, and a premium technology aesthetic."
ninteger | nulldefault: 1

Allowed range: 1–10; start with 1. Fewer images may be delivered than requested. Iterate data[] and use actual-delivery billing.

Required range: 1 <= x <= 10

Example:1
sizestring | nulldefault: 1024x1024

Omit or use auto for automatic sizing. For WxH, both dimensions must be positive multiples of 16, each at most 3840, aspect ratio at most 3:1, total pixels 655360–8294400. Examples are not an exhaustive enum; 2080x2080 also meets the geometry rules. Inspect actual decoded dimensions. Valid parameters do not guarantee an available service.

Example:"1024x1024"
Example: ["auto","1024x1024","2048x2048","2080x2080","3840x2160"]
response_formatenum<string>default: b64_json

Use b64_json.

Available options: b64_json

Example:"b64_json"
output_formatstringdefault: png

Requested output format; png is recommended, but the response is not guaranteed to have been transcoded to PNG. Detect PNG, JPEG, or WebP from actual bytes before choosing an extension. Convert locally if a specific format is required.

Example:"png"
qualityenum<string | null>default: auto

gpt-image-2 accepts auto, low, medium, high. The two 2.5 models also accept xhigh and max. Omit for the model default; JSON null is treated as omitted, while multipart requires a nonempty string. Valid values do not guarantee an available service for every model, size, and editing combination. Acceptance does not guarantee that a higher setting improves image quality.

Available options: auto, low, medium, high, xhigh, max, null

Example:"auto"

Response

200BetterToken billing extension. Settlement follows the actual delivered pixels and billing tier, which can differ from the requested tier. Amounts are decimal strings; use the returned values.

OpenAI-compatible image response. Iterate data[] and read each image from b64_json. Fewer images may be delivered than requested.

createdinteger
Example:1710000000
dataobject[]
Show child attributes

Base64-encoded image content.

Example:"iVBORw0KGgoAAAANSUhEUgAA...(truncated)"

BetterToken billing extension. Settlement follows the actual delivered pixels and billing tier, which can differ from the requested tier. Amounts are decimal strings; use the returned values.

Show child attributes

Settlement currency; do not assume a fixed currency.

Final request amount as a decimal string; use decimal arithmetic for calculations.

Delivered image indices, actual dimensions, billing tiers, and per-image amounts. index is zero-based and corresponds to data[].

Required range: x >= 0

Required range: x >= 1

Required range: x >= 1

Available options: 1K, 2K, 4K

Settled amount for this image as a decimal string.

Resolution tier inferred from the request; it may differ from the actual billing tier.

Example
{
  "created": 1710000000,
  "data": [
    {
      "b64_json": "iVBORw0KGgoAAAANSUhEUgAA...(truncated)",
      "revised_prompt": "<string>"
    }
  ],
  "bettertoken_image_billing": {
    "currency": "<string>",
    "settled_amount": "<string>",
    "outputs": [
      {
        "index": 0,
        "width": 1,
        "height": 1,
        "billing_resolution": "1K",
        "currency": "<string>",
        "amount": "<string>"
      }
    ],
    "partial": false,
    "requested_resolution": "<string>"
  }
}
400Invalid request format, missing parameters, malformed JSON or multipart data, or invalid dimensions.
errorobject
Show child attributes
Example:"invalid request"
Example:"invalid_request_error"
Example:"invalid_request"
messagestring
Example:"insufficient quota"
Example
{
  "error": {
    "message": "invalid request",
    "type": "invalid_request_error",
    "code": "invalid_request"
  },
  "message": "insufficient quota"
}
401The BetterToken API Key is missing or invalid.
errorobject
Show child attributes
Example:"invalid request"
Example:"invalid_request_error"
Example:"invalid_request"
messagestring
Example:"insufficient quota"
Example
{
  "error": {
    "message": "invalid request",
    "type": "invalid_request_error",
    "code": "invalid_request"
  },
  "message": "insufficient quota"
}
402The account has insufficient credit or balance.
errorobject
Show child attributes
Example:"invalid request"
Example:"invalid_request_error"
Example:"invalid_request"
messagestring
Example:"insufficient quota"
Example
{
  "error": {
    "message": "invalid request",
    "type": "invalid_request_error",
    "code": "invalid_request"
  },
  "message": "insufficient quota"
}
403Insufficient balance or token quota: error.code is insufficient_user_quota. Funds must also cover the reservation of the highest-tier unit price times the image-count upper bound, before actual-delivery settlement. Inspect error.code rather than treating every 403 as an authorization error.
errorobject
Show child attributes
Example
{
  "error": {
    "code": "insufficient_user_quota",
    "message": "Insufficient balance or token quota."
  }
}
429A rate or concurrency limit was reached, or the upstream service is busy.
errorobject
Show child attributes
Example:"invalid request"
Example:"invalid_request_error"
Example:"invalid_request"
messagestring
Example:"insufficient quota"
Example
{
  "error": {
    "message": "invalid request",
    "type": "invalid_request_error",
    "code": "invalid_request"
  },
  "message": "insufficient quota"
}
500The gateway or upstream service returned an error.
errorobject
Show child attributes
Example:"invalid request"
Example:"invalid_request_error"
Example:"invalid_request"
messagestring
Example:"insufficient quota"
Example
{
  "error": {
    "message": "invalid request",
    "type": "invalid_request_error",
    "code": "invalid_request"
  },
  "message": "insufficient quota"
}