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.
Recommended values
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-2n=1response_format=b64_jsonoutput_format=pngUse 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 field | Example value |
|---|---|
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 |
image | Select the local file reference.png |
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 field | Example value |
|---|---|
model | gpt-image-2 |
prompt | Combine these reference images into a promotional poster with a consistent style. |
n | 1 |
size | 1024x1024 |
response_format | b64_json |
output_format | png |
image[] | Select local file reference-1.png |
image[] | Select local file reference-2.jpg |
image[] | Select local file reference-3.png |
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.
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 Pathfrom 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 base64from 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)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 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.
| 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. |
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, orgpt-image-2.5-sunburst. - Use
imagefor one reference or repeatedimage[]fields for multiple references. - For masked editing, send one
maskwith the same format and dimensions as the source and an Alpha channel. - 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 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.
Related docs
/v1/images/editsAuthorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Body multipart/form-data
GPT Image Model ID. One BetterToken API Key works with all available models.
"gpt-image-2"Instructions for editing the image or generating from reference images.
"Use this reference image to create a more polished square product hero image while preserving the main style."The reference image field for a single input image.
Repeat this field for each input when sending multiple reference images.
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.
Allowed range: 1–10; start with 1. Fewer images may be delivered than requested. Iterate data[] and use actual-delivery billing.
"1"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.
"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
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[].
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"
}
