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
curlexample, then use Save the image to decode each entry indata[]. - 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:
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" }'Recommended values
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"}| Parameter | What to send | Purpose |
|---|---|---|
model | gpt-image-2, gpt-image-2.5-flare, or gpt-image-2.5-sunburst | Select 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. |
prompt | A text description | Required image description. Include the subject, style, composition, and important constraints. |
size | Omit it, use auto, or provide dimensions such as 1024x1024 | Requests an output size and aspect ratio. Returned dimensions can differ. |
n | Integer from 1 to 10 | Maximum requested image count. 1 is a practical starting point; the response may contain fewer images. |
quality | auto, low, medium, or high for gpt-image-2; xhigh and max are also accepted by the two 2.5 models | Optional 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_format | b64_json | Returns each image as base64 in data[].b64_json. |
output_format | For example, png | Requests 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.
Recommended sizes
size | Ratio | Use case |
|---|---|---|
auto | Auto | Automatic size selection |
1024x1024 | 1:1 | Square images, avatars, covers, assets |
1536x1024 | 3:2 | Landscape posters, banners, scenes |
1024x1536 | 2:3 | Portrait mobile covers and posters |
1536x1152 | 4:3 | Standard landscape images, product images, content graphics |
1152x1536 | 3:4 | Standard portrait images, mobile covers, vertical posters |
2048x2048 | 1:1 | High-resolution square images |
2048x1152 | 16:9 | High-resolution landscape images |
3840x2160 | 16:9 | 4K landscape images |
2160x3840 | 9:16 | 4K 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 base64import jsonfrom 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.
| Field | Meaning |
|---|---|
currency | Currency used for this settlement. Do not assume it is always USD or CNY. |
settled_amount | Total amount settled for this request, represented as a decimal string. |
requested_resolution | Resolution tier inferred from the request. |
outputs[].index | Zero-based image index corresponding to data[]. |
outputs[].width, height | Actual width and height of the corresponding image, in pixels. |
outputs[].billing_resolution | Final billing tier for the corresponding image. |
outputs[].currency, amount | Settlement 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 status | Meaning | What to do |
|---|---|---|
400 | Malformed JSON, a missing parameter, or an unsupported size | Check the request body. Do not retry automatically. |
401 | API Key is missing or invalid | Check the Authorization: Bearer YOUR_API_KEY header. |
402 or 403 | Insufficient quota or balance, including a balance hold | Check error.code and account quota/balance. 403 with error.code=insufficient_user_quota means the balance may be below the preauthorization upper bound. |
429 | Rate limit, concurrency limit, or busy upstream | Retry only after confirming the request was rejected; use backoff. |
5xx | Gateway or upstream failure | Execution 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, orgpt-image-2.5-sunburst. - Use
response_format=b64_json.output_formatis a request value; identify the actual format from the bytes before saving. naccepts integers from1to10. Start with1; fewer images may be returned.- Omit
size, useauto, 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.
Related docs
/v1/images/generationsAuthorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Body application/json
GPT Image Model ID. One BetterToken API Key works with all available models.
"gpt-image-2"A detailed description of the image to generate.
"A futuristic AI product poster on a light background, with glass textures, a clean composition, and a premium technology aesthetic."Allowed range: 1–10; start with 1. Fewer images may be delivered than requested. Iterate data[] and use actual-delivery billing.
1Omit 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.
"1024x1024"["auto","1024x1024","2048x2048","2080x2080","3840x2160"]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.
"png"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.
"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.
1710000000Show child attributes
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[].
Settled amount for this image as a decimal string.
Resolution tier inferred from the request; it may differ from the actual billing tier.
{
"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.
Show child attributes
"invalid request""invalid_request_error""invalid_request""insufficient quota"{
"error": {
"message": "invalid request",
"type": "invalid_request_error",
"code": "invalid_request"
},
"message": "insufficient quota"
}401The BetterToken API Key is missing or invalid.
Show child attributes
"invalid request""invalid_request_error""invalid_request""insufficient quota"{
"error": {
"message": "invalid request",
"type": "invalid_request_error",
"code": "invalid_request"
},
"message": "insufficient quota"
}402The account has insufficient credit or balance.
Show child attributes
"invalid request""invalid_request_error""invalid_request""insufficient quota"{
"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.
{
"error": {
"code": "insufficient_user_quota",
"message": "Insufficient balance or token quota."
}
}429A rate or concurrency limit was reached, or the upstream service is busy.
Show child attributes
"invalid request""invalid_request_error""invalid_request""insufficient quota"{
"error": {
"message": "invalid request",
"type": "invalid_request_error",
"code": "invalid_request"
},
"message": "insufficient quota"
}500The gateway or upstream service returned an error.
Show child attributes
"invalid request""invalid_request_error""invalid_request""insufficient quota"{
"error": {
"message": "invalid request",
"type": "invalid_request_error",
"code": "invalid_request"
},
"message": "insufficient quota"
}
