Skip to content
Sign Up Free
GPT Image

GPT Image Editing API

Edit GPT Image references with BetterToken's OpenAI-compatible API. Configure multipart requests and masks, save results, check billing, and handle errors.

POST /v1/images/edits

The image-to-image endpoint uses a multipart/form-data request body. Upload one or more reference images and submit prompt and parameter fields as form fields. Ordinary reference-image editing does not require a mask. Model and parameter acceptance does not guarantee that a model, size, or edit route is currently available to your request.

These are starting recommendations, not fixed requirements. 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

Use image for a single reference image. Use repeated image[] fields for multiple reference images.

You can use gpt-image-2, gpt-image-2.5-flare, or gpt-image-2.5-sunburst when available for your request. quality accepts auto, low, medium, or high on gpt-image-2; the two 2.5 models also accept xhigh and max. Parameter acceptance alone does not guarantee model or edit-route availability, or that a higher quality setting improves the image.

For multipart requests, quality must be a non-empty string, and size cannot be an empty string. Omit either field when you do not want to specify it. Accepted parameters do not guarantee availability for a model and size combination.

Single reference image

Prepare a real reference image such as reference.png. Replace YOUR_API_KEY with your BetterToken API Key. One key works with all available models. Let curl -F or Python files generate the Content-Type boundary; do not override it manually.

Submit these form fields:

Form fieldExample value
modelgpt-image-2
promptUsing this reference image, create a more polished square product key visual while preserving the subject style.
n1
size1024x1024
response_formatb64_json
output_formatpng
imageSelect the local file reference.png
Terminal window
curl --max-time 300 'https://bettertoken.ai/v1/images/edits' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-F 'model=gpt-image-2' \
-F 'prompt=Using this reference image, create a more polished square product key visual while preserving the subject style.' \
-F 'n=1' \
-F 'size=1024x1024' \
-F 'response_format=b64_json' \
-F 'output_format=png' \
-F 'image=@reference.png'

Multiple reference images

Submit these form fields:

Form fieldExample value
modelgpt-image-2
promptCombine these reference images into a promotional poster with a consistent style.
n1
size1024x1024
response_formatb64_json
output_formatpng
image[]Select local file reference-1.png
image[]Select local file reference-2.jpg
image[]Select local file reference-3.png
Terminal window
curl --max-time 300 'https://bettertoken.ai/v1/images/edits' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-F 'model=gpt-image-2' \
-F 'prompt=Combine these reference images into a promotional poster with a consistent style.' \
-F 'n=1' \
-F 'size=1024x1024' \
-F 'response_format=b64_json' \
-F 'output_format=png' \
-F 'image[]=@reference-1.png' \
-F 'image[]=@reference-2.jpg' \
-F 'image[]=@reference-3.png'

Edit a selected area with a mask

Add the optional mask file field when you want to guide editing to a selected area. The current BetterToken endpoint reads one mask file. Do not send repeated mask[] fields.

  • The original image and mask must use the same file format and pixel dimensions. Each file must be smaller than 50 MB. For maximum compatibility, use PNG with an Alpha channel for both files.
  • The mask must contain an Alpha channel. Fully transparent pixels (Alpha 0) mark the area the model may redraw. Fully opaque pixels (Alpha 255) guide the model to preserve that area.
  • A regular RGB black-and-white image is not a valid mask unless it actually contains an Alpha channel.
  • If you send multiple image[] fields, the mask applies to the first input image.
  • The mask guides the model. It does not guarantee that pixels outside the transparent area remain absolutely identical.
  • For product images, logos, or precise retouching, inspect the unmasked areas before using the result. GPT Image 2 and Flare have shown visible changes outside the target region; localized control with Sunburst has not been verified.
Terminal window
curl --max-time 300 'https://bettertoken.ai/v1/images/edits' \
-H 'Authorization: Bearer YOUR_API_KEY' \
-F 'model=gpt-image-2' \
-F 'prompt=Replace only the masked area with a blue circle. Preserve everything else.' \
-F 'n=1' \
-F 'size=1024x1024' \
-F 'response_format=b64_json' \
-F 'output_format=png' \
-F 'image=@original.png' \
-F 'mask=@mask.png'

Masked edit with Python

Open both files in one with statement so Python closes them after the request:

import requests
url = "https://bettertoken.ai/v1/images/edits"
headers = {"Authorization": "Bearer YOUR_API_KEY"}
data = {
"model": "gpt-image-2",
"prompt": "Replace only the masked area with a blue circle. Preserve everything else.",
"n": "1",
"size": "1024x1024",
"response_format": "b64_json",
"output_format": "png",
}
with open("original.png", "rb") as image_file, open("mask.png", "rb") as mask_file:
files = {
"image": ("original.png", image_file, "image/png"),
"mask": ("mask.png", mask_file, "image/png"),
}
response = requests.post(url, headers=headers, data=data, files=files, timeout=300)
response.raise_for_status()
result = response.json()

Check the files before upload

This minimal Pillow check verifies the required format, dimensions, and Alpha channel. For a typical local mask, it also warns when the Alpha channel does not include both fully transparent and fully opaque areas:

from pathlib import Path
from PIL import Image
image_path = Path("original.png")
mask_path = Path("mask.png")
if image_path.stat().st_size >= 50 * 1024 * 1024 or mask_path.stat().st_size >= 50 * 1024 * 1024:
raise ValueError("Each file must be smaller than 50 MB")
with Image.open(image_path) as image, Image.open(mask_path) as mask:
if image.format != mask.format:
raise ValueError("The image and mask formats must match")
if image.size != mask.size:
raise ValueError("The image and mask dimensions must match")
if "A" not in mask.getbands():
raise ValueError("The mask must contain an Alpha channel")
alpha_min, alpha_max = mask.getchannel("A").getextrema()
if alpha_min > 0 or alpha_max < 255:
print("Warning: for a typical local mask, include fully transparent and fully opaque areas")

Python example

import base64
from pathlib import Path
import requests
url = "https://bettertoken.ai/v1/images/edits"
headers = {
"Authorization": "Bearer YOUR_API_KEY",
}
data = {
"model": "gpt-image-2",
"prompt": "Using this reference image, create a more polished square product key visual while preserving the subject style.",
"n": "1",
"size": "1024x1024",
"response_format": "b64_json",
"output_format": "png",
}
with open("reference.png", "rb") as image_file:
files = {
"image": ("reference.png", image_file, "image/png"),
}
response = requests.post(url, headers=headers, data=data, files=files, timeout=300)
response.raise_for_status()
result = response.json()
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)
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 or set it to auto. 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. Treat output_format as a request, not a guarantee or conversion instruction; inspect image bytes and use the matching file extension. The text-to-image page includes standard-library Python and Node.js examples that detect PNG, JPEG, and WebP signatures and save every returned image.

Read the actual cost

bettertoken_image_billing is returned with the edit 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.

Requested dimensions and the 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 result = response.json(). 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/edits, 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 the edit 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 an image-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. Monetary values are decimal strings; the response can contain fewer images than n.

Integration checklist

  • The Base URL is https://bettertoken.ai/v1.
  • The header includes Authorization: Bearer sk-....
  • The request uses multipart/form-data.
  • Choose a Model ID available for this request: gpt-image-2, gpt-image-2.5-flare, or gpt-image-2.5-sunburst.
  • Use image for one reference or repeated image[] fields for multiple references.
  • For masked editing, send one mask with the same format and dimensions as the source and an Alpha channel.
  • 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 supply dimensions that meet the constraints.
  • 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/edits

Authorizations

Authorizationstringheaderrequired

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

Body multipart/form-data

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

Instructions for editing the image or generating from reference images.

Example:"Use this reference image to create a more polished square product hero image while preserving the main style."
imagestring<binary>

The reference image field for a single input image.

image[]string[]

Repeat this field for each input when sending multiple reference images.

maskstring<binary>

An optional single mask file. The mask and original image must have the same format and dimensions, each file must be smaller than 50 MB, and the mask must contain an Alpha channel. For maximum compatibility, use PNG with an Alpha channel for both files. With multiple image[] inputs, the mask applies to the first image. Availability depends on the model and size combination. Ordinary reference-image edits do not require a mask. A mask guides generation but does not guarantee that pixels outside the target area remain unchanged; review the result.

ninteger | stringdefault: 1

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

Example:"1"
sizestringdefault: 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>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

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)"

An optional revised prompt.

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"
}