curl --request POST \
--url https://api.apiyi.com/v1/images/edits \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form model=gpt-image-2 \
--form 'prompt=Place subject from image 1 into scene from image 2, using color style from image 3' \
--form 'image=<string>' \
--form image.items='@example-file' \
--form mask='@example-file'import requests
url = "https://api.apiyi.com/v1/images/edits"
files = {
"image.items": ("example-file", open("example-file", "rb")),
"mask": ("example-file", open("example-file", "rb"))
}
payload = {
"model": "gpt-image-2",
"prompt": "Place subject from image 1 into scene from image 2, using color style from image 3",
"image": "<string>"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('model', 'gpt-image-2');
form.append('prompt', 'Place subject from image 1 into scene from image 2, using color style from image 3');
form.append('image', '<string>');
form.append('image.items', '{
"fileName": "example-file"
}');
form.append('mask', '{
"fileName": "example-file"
}');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.apiyi.com/v1/images/edits', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.apiyi.com/v1/images/edits",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\ngpt-image-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nPlace subject from image 1 into scene from image 2, using color style from image 3\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image.items\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"mask\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.apiyi.com/v1/images/edits"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\ngpt-image-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nPlace subject from image 1 into scene from image 2, using color style from image 3\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image.items\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"mask\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.apiyi.com/v1/images/edits")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\ngpt-image-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nPlace subject from image 1 into scene from image 2, using color style from image 3\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image.items\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"mask\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.apiyi.com/v1/images/edits")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\ngpt-image-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nPlace subject from image 1 into scene from image 2, using color style from image 3\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image.items\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"mask\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"created": 1776832476,
"data": [
{
"b64_json": "iVBORw0KGgoAAAANSUhEUgAA..."
}
],
"usage": {
"input_tokens": 1280,
"output_tokens": 6240,
"total_tokens": 7520
}
}画像編集 API リファレンス
gpt-image-2 画像編集 API リファレンスとライブテスト — 参照画像を最大16枚アップロード + 単一画像編集、複数画像の融合、またはマスク補完のための指示
curl --request POST \
--url https://api.apiyi.com/v1/images/edits \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form model=gpt-image-2 \
--form 'prompt=Place subject from image 1 into scene from image 2, using color style from image 3' \
--form 'image=<string>' \
--form image.items='@example-file' \
--form mask='@example-file'import requests
url = "https://api.apiyi.com/v1/images/edits"
files = {
"image.items": ("example-file", open("example-file", "rb")),
"mask": ("example-file", open("example-file", "rb"))
}
payload = {
"model": "gpt-image-2",
"prompt": "Place subject from image 1 into scene from image 2, using color style from image 3",
"image": "<string>"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('model', 'gpt-image-2');
form.append('prompt', 'Place subject from image 1 into scene from image 2, using color style from image 3');
form.append('image', '<string>');
form.append('image.items', '{
"fileName": "example-file"
}');
form.append('mask', '{
"fileName": "example-file"
}');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.apiyi.com/v1/images/edits', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.apiyi.com/v1/images/edits",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\ngpt-image-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nPlace subject from image 1 into scene from image 2, using color style from image 3\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image.items\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"mask\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.apiyi.com/v1/images/edits"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\ngpt-image-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nPlace subject from image 1 into scene from image 2, using color style from image 3\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image.items\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"mask\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.apiyi.com/v1/images/edits")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\ngpt-image-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nPlace subject from image 1 into scene from image 2, using color style from image 3\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image.items\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"mask\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.apiyi.com/v1/images/edits")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"model\"\r\n\r\ngpt-image-2\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"prompt\"\r\n\r\nPlace subject from image 1 into scene from image 2, using color style from image 3\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"image.items\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"mask\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n{\r\n \"fileName\": \"example-file\"\r\n}\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"created": 1776832476,
"data": [
{
"b64_json": "iVBORw0KGgoAAAANSUhEUgAA..."
}
],
"usage": {
"input_tokens": 1280,
"output_tokens": 6240,
"total_tokens": 7520
}
}Bearer sk-xxx)、画像 / マスクファイルを選択し、prompt と model を入力して、送信してください。multipart/form-data です。純粋な text-to-image には、Text-to-Image エンドポイント を使用してください。请求时发生错误: unable to complete request が表示される場合があります — リクエスト自体は成功しています。ブラウザーが、そのように長い base64 文字列を表示できないだけです。推奨ワークフロー(初心者向け):- 下の Python / Node.js / cURL サンプルをコピーしてローカルで実行してください。コードはレスポンスを自動的に
base64.b64decodeし、画像をファイルに書き出します。 - ブラウザー内のプレイグラウンドを使う必要がある場合は、小さな参照画像(< 50KB) を使い、
sizeを最小ティア(例:1024x1024)に設定し、qualityをlowに設定してください。
input_fidelityは渡さないでください —gpt-image-2は高忠実度を強制するため、渡すと 400 が返ります- 編集リクエストは input tokens がかなり多くなります — 参照画像は Vision の課金で多くの tokens に変換されるため、予算を見込んでください
background: transparentはサポートされていません —opaqueを使うか、後処理してください- 複数画像の融合: 最大 16 枚 —
image[]フィールドを繰り返し指定してください。16 を超えるとエラーになります
image[] フィールドは複数の参照画像を受け付けます。アップロード順が、プロンプト内の「image 1 / image 2 / image 3」の参照に対応します。明示的に参照してください:image 1 の被写体を image 2 のシーンに配置し、image 3 の色スタイルを使う1ファイルあたりの上限: 各 50MB 未満(multipart ファイルアップロード)、形式:
png / jpg / webp; 実際にはアップロード前に 1.5MB 以内 まで圧縮してください(下の「アップロードサイズの上限」を参照)。コード例
Python (OpenAI SDK · 単一画像編集)
from openai import OpenAI
import base64
client = OpenAI(
api_key="sk-your-api-key",
base_url="https://api.apiyi.com/v1"
)
resp = client.images.edit(
model="gpt-image-2",
image=open("photo.png", "rb"),
prompt="Replace the background with a seaside sunset, preserve subject details",
size="1536x1024",
quality="high"
)
# b64_json is raw base64 (no prefix) — decode manually
with open("edited.png", "wb") as f:
f.write(base64.b64decode(resp.data[0].b64_json))
Python (OpenAI SDK · 複数画像融合)
resp = client.images.edit(
model="gpt-image-2",
image=[
open("person.png", "rb"),
open("scene.png", "rb"),
open("style.png", "rb"),
],
prompt="Place subject from image 1 into scene from image 2, using color style from image 3, keep lighting consistent",
size="1536x1024",
quality="high"
)
with open("fused.png", "wb") as f:
f.write(base64.b64decode(resp.data[0].b64_json))
cURL (複数画像融合)
curl -X POST "https://api.apiyi.com/v1/images/edits" \
-H "Authorization: Bearer sk-your-api-key" \
-F "model=gpt-image-2" \
-F "prompt=Place subject from image 1 into scene from image 2, using color style from image 3" \
-F "size=1536x1024" \
-F "quality=high" \
-F "image[][email protected]" \
-F "image[][email protected]" \
-F "image[][email protected]"
cURL (マスクのインペインティング)
curl -X POST "https://api.apiyi.com/v1/images/edits" \
-H "Authorization: Bearer sk-your-api-key" \
-F "model=gpt-image-2" \
-F "prompt=Replace the sky with pink sunset clouds" \
-F "size=1024x1024" \
-F "quality=high" \
-F "image[][email protected]" \
-F "[email protected]" \
| jq -r '.data[0].b64_json' | base64 -d > photo_edited.png
Node.js (ネイティブ fetch + FormData · 複数画像融合)
import fs from 'node:fs';
const form = new FormData();
form.append('model', 'gpt-image-2');
form.append('prompt', 'Place subject from image 1 into scene from image 2');
form.append('size', '1536x1024');
form.append('quality', 'high');
form.append('image[]', new Blob([fs.readFileSync('./person.png')]), 'person.png');
form.append('image[]', new Blob([fs.readFileSync('./scene.png')]), 'scene.png');
const resp = await fetch('https://api.apiyi.com/v1/images/edits', {
method: 'POST',
headers: { 'Authorization': 'Bearer sk-your-api-key' },
body: form
});
const { data } = await resp.json();
fs.writeFileSync('fused.png', Buffer.from(data[0].b64_json, 'base64'));
パラメータリファレンス
| 項目 | 型 | 必須 | デフォルト | 説明 |
|---|---|---|---|---|
model | text | Yes | — | 固定値: gpt-image-2 |
prompt | text | Yes | — | 編集 / 合成指示 |
image[] | file | Yes | — | 参照画像、複数指定可 (最大 16) |
mask | file | No | — | マスク画像(最初の画像にのみ適用、alpha channel が必要) |
size | text | No | auto | 出力サイズ、text-to-image と同じ |
quality | text | No | auto | low / medium / high / auto |
output_format | text | No | png | png / jpeg / webp |
output_compression | text | No | — | 0–100、jpeg / webp のみ |
background | text | No | auto | auto / opaque (not supported: transparent) |
standard / hd を quality に渡さないでください。 受け付けられるのは、4つの公式な enum 値 low / medium / high / auto のみです。レガシー値はバックエンドチャネル間で挙動が一貫せず、即座に 400(invalid_value)で失敗することもあれば、黙って無視されてリクエストが auto で実行されることもあります(コストが予測不能になります)。必ず4つの公式値のいずれかを明示的に指定してください。アップロードサイズの制限
| 項目 | 制限 | 備考 |
|---|---|---|
| 参照画像数 | 最大 16 枚 | image[] フィールドを繰り返してください |
| 画像ごと(multipart ファイルアップロード) | 各 50MB 未満 | 形式: png / jpg / webp |
| 画像ごと(base64 data URL) | フィールド長 約20MiB | これは URL/base64 の 文字列フィールド の長さ制限(schema maxLength: 20971520)であり、50MB の multipart 上限とは 別 です。base64 によりサイズは約 1/3 増えるため、元画像は 15MB 以内 に収めてください |
| マスクファイル | 4MB 未満の PNG | 元画像と同じ寸法で、アルファチャンネルが必要です |
参照画像の形式要件と前処理
/v1/images/edits は png / jpg / webp の標準形式のみを受け付けます。次の 400 が返ってきた場合:
{
"error": {
"message": "Invalid image file or mode for image 1, please check your image file. ...",
"type": "shell_api_error",
"code": "invalid_image_file"
}
}
.jpg Huawei Mateシリーズの端末からそのまま出力されたファイルには HDR のゲインマップ用サブフレームが埋め込まれており、実際には MPO です。これらのファイルは同じ FFD8 ヘッダーで始まります。拡張子と file コマンドのどちらも JPEG と表示されるため、見た目では判別できません。フレームを認識できるパース(例: Pillow)だけが見分けられます。エラーにある「image 1」は N 番目の参照画像(1 始まり)を指すので、インデックスを使って問題のファイルを特定してください。
Image.open(f).format が "MPO" を返す場合、ファイルの変換が必要です。アップロードパイプラインに 1 回の再エンコード手順を入れておけば、HEIC や他のスマホ形式にも対応できます:
from PIL import Image
import io
def normalize_image(path: str) -> bytes:
"""Convert phone photos (MPO/HDR multi-frame etc.) to standard JPEG that passes edits validation"""
im = Image.open(path)
im.load() # for MPO, keeps only the first (full-size) frame
if im.mode not in ("RGB", "RGBA"):
im = im.convert("RGB")
out = io.BytesIO()
im.save(out, format="JPEG", quality=92) # or format="PNG"
return out.getvalue()
マスク インペインティング要件
- 元画像と同じサイズ、PNG形式、4MB未満
- アルファチャンネル必須: 透明(alpha=0)= インペイント対象領域、不透明 = 保持
- マスクは最初の画像にのみ適用されます
- マスクは「ソフトガイド」です — モデルはマスクされた領域の周囲を拡張または縮小する場合があります
image[]としてフィードバックし、新しい指示で段階的に調整します。各ラウンドは個別に token 課金されます — 累積コストに注意してください。Response Format
{
"created": 1776832476,
"data": [
{
"b64_json": "iVBORw0KGgoAAAANSUhEUgAA..."
}
],
"usage": {
"input_tokens": 848,
"input_tokens_details": {
"image_tokens": 832,
"text_tokens": 16
},
"output_tokens": 196,
"output_tokens_details": {
"image_tokens": 196,
"text_tokens": 0
},
"total_tokens": 1044
}
}
b64_json is raw base64, without the data:image/...;base64, prefix — different from gpt-image-2-all. Decode it client-side to write a file, or prepend the prefix for browser rendering.input_tokens are typically significantly higher than text-to-image at the same size, because reference images are billed per Vision pricing rules — the exact amount is available directly in usage.input_tokens_details.image_tokens, tracked separately from the text portion (text_tokens). Multi-image fusion increases image_tokens strictly linearly per additional reference image (verified July 2026: 4 × 1024² images = 4 × 1024 tokens) — see How Multiple Input Images Affect the Price for the measurement table. See How to check the real token count for each call on the overview page for the full field reference.承認
API Key obtained from APIYI Console
ボディ
Model name, fixed as gpt-image-2
gpt-image-2 Edit/fusion instruction. For multi-image, use 'image 1 / image 2 / image 3' to reference upload order
"Place subject from image 1 into scene from image 2, using color style from image 3"
Reference images. For a single image, send the field once; for multiple images, repeat the same image field (e.g., -F [email protected] -F [email protected], max 16) — upload order maps to image 1 / image 2 / ... in the prompt. multipart file upload: each under 50MB, formats: png/jpg/webp; compress to within 1.5MB in practice
Mask image (optional, only applies to first image). Requirements:
- Same size as original
- PNG format, under 4MB
- Must have alpha channel (alpha=0 = inpaint area, opaque = preserve)
Output size (same as text-to-image). Preset or constraint-satisfying custom size
"1536x1024"
Quality tier
auto, low, medium, high Output format
png, jpeg, webp Output compression (0–100), only effective for jpeg/webp
0 <= x <= 100Background mode. auto or opaque. Not supported: transparent
auto, opaque このページは役に立ちましたか?