料金プラン+7% ボーナス

ChatGPTで複数の画像を一度に生成する

Python、CSV ファイル、安全な並行処理、再試行、Base64 デコード、コスト管理を使って、GPT Image 2 API で商品画像を一括生成する方法を学びます。

ChatGPTで複数の画像を一度に生成する

100個の商品を含むスプレッドシートがあるとします。各行には商品名、色、素材、希望するカメラアングルがあります。それらの詳細をChatGPTに100回コピーする代わりに、Pythonスクリプトにスプレッドシートを読み取らせ、各行ごとに商品画像を1枚リクエストできます。

これがこのガイドでいう「一括商品画像生成」です。これは1枚の画像の10種類のバリエーションを求めることとは異なります。各商品には独自のプロンプト、タスクID、出力ファイル、ステータス記録があります。

このワークフローは、GPTProto上のGPT Image 2 APIを使ってPythonで構築します。画像APIの経験は不要です。最終的なスクリプトはCSVカタログを読み取り、画像サイズを確認し、一度に少数の商品を処理し、結果をダウンロードし、完了した作業を記憶するため、再起動しても同じ商品を再度生成しません。

要約すると:

  1. 商品ごとに異なるプロンプトには n=1 を使用します。

  2. 小さなワーカープールで複数の異なるSKUジョブを送信します。

  3. 画像を待つ前に、返された予測IDを保存します。

  4. ジョブが完了したらすぐに一時的な出力URLをダウンロードします。

  5. 成功した商品を再度送信する代わりに、マニフェストから再開します。

「予測ID」「ポーリング」「マニフェスト」などの用語に馴染みがなくても心配しないでください。完全なスクリプトが登場する前に、以下で説明します。

目次

最終的にできるもの

最初に2つのファイルを用意します:

  • products.csv: 処理したい商品のスプレッドシートのようなリスト。

  • bulk_product_images.py: このガイドのPythonスクリプト。

スクリプトを実行すると、さらに2つの項目が自動的に作成されます:

  • generated/: MUG-001-01.png などのファイルを含むフォルダ。

  • manifest.json: どの商品が完了し、失敗し、またはまだ確認が必要かを示す記録。

流れはシンプルです:

products.csv → Python script → GPT Image 2 API → ダウンロードされた画像
                         ↘ manifest.json

このガイドの3つのサンプル商品から始めてください。それらがうまくいったら、サンプル行を実際のカタログに置き換えます。

このガイドで使用される初心者向け用語

用語 わかりやすい意味
API スクリプトがチャットウィンドウを使わずに GPT Image 2 に指示を送る方法。
APIキー GPT Protoアカウントを識別し、APIの使用を認証する非公開のパスワード。
SKU 1つの商品に対する独自の一意のコード(例: MUG-001)。出力ファイル名にも使用されます。
リクエスト スクリプトからAPIに送信される1つのメッセージ。このワークフローでは、通常1つの商品が1つの生成リクエストを作成します。
タスクまたは予測ID 生成リクエストが受け付けられた後に返される受付番号。スクリプトはこれを使用して結果を確認します。
ポーリング 数秒ごとに結果エンドポイントにタスクが完了したかどうかを問い合わせること。
同時実行 同時に処理される商品の数。ワーカーが3つなら、最大3つのアクティブなジョブを意味します。
マニフェスト すべてのSKU、タスクID、結果、エラーのローカル記録。停止したスクリプトを安全に再開できます。
Base64 長いテキスト文字列として表される画像データ。通常の画像ファイルになる前にデコードする必要があります。

APIワークフローは、レストランで注文を受け取るのに似ています。最初のリクエストで注文します。返されるタスクIDは整理番号です。ポーリングで注文の準備ができたか確認します。出力URLは、完成した品を受け取る場所です。マニフェストは領収書綴りです。

ChatGPTは複数の商品画像を一度に生成できますか?

ChatGPTは会話全体で複数の画像を作成できるため、視覚的な探索や少数の手動バリエーションには適しています。しかし、信頼できるカタログパイプラインではありません。CSVキュー、安定したSKUとファイルのマッピング、同時実行制御、再試行マニフェストがありません。

「複数の商品画像を作成し、どのファイルがどの商品に属するかを知るにはどうすればよいですか?」という問いには、APIの方が適しています。

ニーズ ChatGPTインターフェース GPT Image 2 APIワークフロー
1つのコンセプトを探索 適している 可能だが、より多くの設定が必要
CSVカタログを処理 手動 自動
SKUごとに異なるプロンプトを使用 繰り返し 1行が1リクエストになる
ファイル名とフォルダを制御 手動ダウンロード 決定論的な命名
失敗した作業を再開 手動レビュー マニフェストベース
DAM、PIM、またはストアと統合 限定的 コード統合用に設計

「ChatGPT generate multiple images at once」のような検索では、1つのプロンプトの複数のバリエーションと、異なる商品の多数の画像という2つの異なる目的が混同されがちです。コスト、ファイルの所有権、再実行が関係してくると、この区別は重要になります。

「GPT Image 2 APIバッチ」が意味するもの

人々は「バッチ」を3つの異なるパターンで使用します:

1. 1つのプロンプトから複数のバリエーション

設定: n を 1 と 10 の間に設定します。これは、デザイナーが同じ商品と構図に対して複数の候補を必要とする場合に便利です。10個のSKUに対して商品固有のプロンプトを作成するわけではありません。

2. 同時に複数の独立したリクエスト

ファイルから複数の商品を読み取り、1行につき1つのリクエストを送信します。小さな ThreadPoolExecutor が制御された同時実行を提供します。これはこのチュートリアルで使用されるパターンです。各商品が独自の名前、色、素材、角度、背景を持つことができるためです。

3. OpenAIの非同期Batch API

OpenAIの直接Batch APIは、JSONLリクエストファイルを受け入れ、別のレートプールを使用し、50%の割引を提供し、24時間以内に完了します。OpenAI Batch APIガイド には、サポートされているエンドポイントとして /v1/images/generations と /v1/images/edits が記載されています。

これは別のOpenAI機能です。同じJSONL BatchエンドポイントがGPT Protoに存在すると仮定しないでください。以下のGPT Protoワークフローでは、文書化されている画像エンドポイントと予測ステータスルートを使用します。

必要なもの

コンピューター、Python 3、GPT Proto APIキー、テキストエディターが必要です。Visual Studio Codeが便利ですが、プレーンテキストファイルを保存できるエディターならどれでも機能します。

1. Pythonがインストールされていることを確認する

macOSまたはLinuxではターミナルを、WindowsではPowerShellを開き、次を実行します:

python --version

Windowsで python が認識されないと表示される場合は、次を試してください:

py --version

Python 3.11.8 のような結果が表示されれば、Pythonの準備は完了です。どちらのコマンドも機能しない場合は、最新のPython 3リリースをインストールし、Pythonをシステムパスに追加するオプションを有効にしてください。

2. プロジェクトフォルダを作成する

gpt-image-bulk という名前のフォルダを作成します。その中に、次の2つの空のファイルを作成します:

  • bulk_product_images.py

  • products.csv

このガイドの完全なPythonスクリプトを最初のファイルに貼り付けます。以下に示すサンプルCSVを2番目のファイルに貼り付けます。スクリプト内のパスを変更しない限り、両方のファイルは同じフォルダにある必要があります。

3. APIキーを作成して保存する

GPT Proto でアカウントを作成し、クレジットを追加して、APIキーを生成します。APIキーはパスワードのように扱う必要があります。記事に公開したり、GitHubにアップロードしたり、Pythonファイルに直接貼り付けたりしないでください。

キーを一時的に環境変数に保存します。macOSまたはLinuxでは、次を実行します:

export GPTPROTO_API_KEY="your-key-here"

Windows PowerShellでは、次を実行します:

$env:GPTPROTO_API_KEY="your-key-here"

変数は通常、現在のターミナルウィンドウでのみ有効です。そのウィンドウを閉じて別のウィンドウを開いた場合は、スクリプトを実行する前に再度設定してください。

4. 必要なPythonパッケージを1つインストールする

スクリプトは requests を使用してAPIと通信し、画像をダウンロードします。次でインストールします:

python -m pip install requests

Windowsユーザーで py コマンドが必要だった場合は、代わりに py -m pip install requests を実行してください。

5. 商品をCSVに追加する

CSVは「comma-separated values(カンマ区切り値)」を意味します。Excel、Googleスプレッドシート、およびほとんどのカタログツールがエクスポートできるシンプルな表計算形式です。1行につき1商品を追加します:

1商品につき1行で products.csv を作成します:

sku,name,color,material,angle,background,avoid
MUG-001,Stackable coffee mug,matte navy,stoneware,three-quarter,soft warm gray,steam or hands
BAG-014,Compact crossbody bag,forest green,pebbled leather,front three-quarter,light beige,props or a model
LAMP-207,Portable table lamp,cream,powder-coated aluminum,front,clean white,visible cable or extra objects

各列には1つの役割があります:

列 入力内容 例
sku 一意の商品コード MUG-001
name 短い商品名 Stackable coffee mug
color 目に見える正確な色または仕上げ matte navy
material 主な目に見える素材 stoneware
angle 希望するカメラビュー three-quarter
background カタログの背景 soft warm gray
avoid 画像に含めるべきでない物体やミス steam or hands

ヘッダー行は、Pythonスクリプトがそれらの列名を探すため、表示されているとおりに正確に保ってください。すべてのSKUは一意でなければなりません。商品名やフィールドにカンマが含まれる場合は、値が正しく引用されるように、表計算ソフトからファイルをエクスポートしてください。

各行に異なる長いプロンプトを書くのではなく、短く事実に基づいたフィールドを使用してください。スクリプトは同じ撮影ルールでフィールドを組み合わせるため、カタログの見た目が一貫します。

ステップ1:再利用可能な商品プロンプトを作成する

商品プロンプトは、SKUごとに変わる事実と、カタログ全体に適用されるルールを分離する必要があります。これにより、プロンプトの改訂が測定可能になります。共有テンプレートを一度変更し、小さなテストセットを再生成して、以前のバージョンと比較します。

def build_prompt(row):
    return f"""Create one catalog product photograph.
Product: {row['name']}
Color: {row['color']}
Material: {row['material']}
View: {row['angle']}
Background: {row['background']}

Keep the product centered with realistic proportions and material texture.
Use soft studio lighting, a natural contact shadow, and generous crop-safe space.
No text, logos, labels, people, or extra objects.
Also avoid: {row.get('avoid', 'none')}.
Return a single product image, not a collage or contact sheet."""

この関数を商品ごとに編集する必要はありません。スクリプトがマグの行を読み取ると、{row['name']} は Stackable coffee mug になります。バッグの行を読み取ると、同じプレースホルダーは Compact crossbody bag になります。これはテンプレートと呼ばれます。構造は同じまま、商品データが変わります。

「単一の商品画像」という指示は、モデルがリクエストをコラージュに変えてしまうのを防ぐのに役立ちます。カメラビュー、クロップ、ライティング、影、背景に関する共有の指示は、別々の商品が無関係な画像ではなく1つのカタログのように見えるのに役立ちます。

初期の結果が一貫しない場合は、まず共有ルールを編集してください。たとえば、「十分なクロップ安全領域」を「商品が正方形キャンバスの約75%を占めるようにする」に置き換えます。その変更をファイル全体に適用する前に、3つの異なる商品タイプでテストしてください。

実際のブランド商品の場合、テキストから画像生成はコンセプトショットや一般的な商品に最適です。正確な形状、パッケージ、ラベル配置が重要な場合は、承認された商品リファレンスを使用した画像編集を使用してください。そのワークフローは後で説明します。

ステップ2:キューを開始する前に1つの商品をテストする

まず1つの商品から始め、n=1、quality="low"、そして一般的なサイズ(例:1024x1024。低品質は、最終品質の生成にコストをかける前に構図を確認するのに便利です。

以下のcURLコマンドは小さなAPIテストです。cURLはWebリクエストを送信するコマンドラインツールです。このコマンドはBash構文を使用しているため、APIキーを設定した後、macOSまたはLinuxのターミナルに貼り付けてください。Windowsの初心者は、手動のcURLテストをスキップして、このガイドの後半にあるクロスプラットフォームのPythonスクリプトを使用できます。

curl --request POST \
  --url "https://gptproto.com/api/v3/openai/gpt-image-2/text-to-image" \
  --header "Authorization: Bearer $GPTPROTO_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "prompt": "中央に配置されたマットなネイビーのストーンウェア製コーヒーマグ、カタログ用の斜め三分割ビュー、柔らかいスタジオ照明、温かみのあるグレーの背景、自然な接地影、テキストや小道具なし",
    "n": 1,
    "quality": "low",
    "size": "1024x1024",
    "enable_sync_mode": false,
    "response_format": "url"
  }'

重要な行の意味は以下のとおりです:

フィールド 意味
Authorization GPT Protoがどのアカウントがリクエストを行っているかを認識できるように、プライベートAPIキーを送信します。
prompt 欲しい商品画像を記述します。
n: 1 このプロンプトに対して1つの結果をリクエストします。
quality: low 最初のテストでは、低コストのドラフト設定を使用します。
size: 1024x1024 正方形の画像をリクエストします。
enable_sync_mode: false 生成が完了するまでリクエストを開いたままにする代わりに、最初にタスクの受領書を返します。
response_format: url 画像の準備ができたらダウンロードリンクを返します。

最初のレスポンスが成功すると、予測IDを含むJSONオブジェクトが返されます。予測IDはdata.idにあります。JSONはAPIで使用される構造化テキスト形式です。短縮例は次のようになります:

{
  "data": {
    "id": "abc123",
    "status": "created",
    "urls": {
      "get": "https://gptproto.com/api/v3/predictions/abc123/result"
    }
  }
}

このレスポンスは画像ではありません。ジョブが受け付けられたことを確認するものです。返された実際のIDをコピーして、その結果を照会します:

curl --request GET \
  --url "https://gptproto.com/api/v3/predictions/abc123/result" \
  --header "Authorization: Bearer $GPTPROTO_API_KEY"

次の値を置き換えます: abc123 をタスクIDに置き換えます。もし data.status がまだ created または processing の場合、数秒待ってからGETコマンドを再度実行してください。それが completed になると、data.outputs に生成された出力が含まれます。完全なPythonスクリプトはこれらのチェックを自動的に行うため、商品ごとにこの手動プロセスを繰り返す必要はありません。

現在のリクエストとレスポンスのフィールドについては、GPT Proto GPT Image 2 テキストから画像へのリファレンスを参照してください。

1つの商品がこれらのチェックに合格するまで、カタログ全体を開始しないでください:

  • トリミングは、すべてのストアフロントの配置に十分な余白を残します。

  • 色と素材は、意図された用途に十分近いです。

  • 背景と接地影がカタログのスタイルに一致しています。

  • 捏造されたロゴ、ラベル、小道具、または重複した商品が表示されていません。

  • 結果のルートとダウンロードコードの両方が、お使いの環境で動作します。

テストが401 Unauthorizedを返す場合、最初に確認すべきはAPIキーとAuthorizationヘッダーです。もし403を返す場合は、モデルへのアクセスとアカウント残高を確認してください。この1商品のテストが成功するまで、カタログ全体に進まないでください。

ステップ3:「サイズは16の倍数でなければなりません」エラーを防ぐ

GPT Image 2は一般的なプリセットと有効なカスタム寸法を受け入れます。カスタムサイズの場合、両辺が16の倍数である必要があり、最長辺は3,840ピクセルを超えることはできず、アスペクト比は3:1を超えることはできず、画像には655,360~8,294,400ピクセルが含まれている必要があります。合計解像度が2560×1440を超える出力は、APIドキュメントで実験的とマークされています。

「両辺が16の倍数でなければならない」とは、幅と高さがそれぞれ16で割り切れる必要があることを意味します。1024、1200、1536、2048などの数値はこのチェックに合格します。1000などの数値は合格しません。このルールは最長辺だけでなく、幅と高さの両方に適用されます。

有料リクエストを送信する前に検証してください:

def validate_size(size):
    if size == "auto":
        return

    width, height = map(int, size.lower().split("x"))
    pixels = width * height

    if width % 16 or height % 16:
        raise ValueError("GPT Image 2の幅と高さは16の倍数である必要があります")
    if max(width, height) > 3840:
        raise ValueError("最長辺は3840ピクセルを超えることはできません")
    if max(width, height) / min(width, height) > 3:
        raise ValueError("アスペクト比は3:1を超えることはできません")
    if not 655_360 <= pixels <= 8_294_400:
        raise ValueError("総ピクセル数がサポート範囲外です")

1200x1200は両辺が16で割り切れるため有効です。1000x1000は無効です。もしストアフロントが最終的に1000x1000を必要とする場合は、1024x1024で生成し、承認後にリサイズしてください。

サポートされているプリセットには、以下のものがあります:1024x1024、1536x1024、1024x1536、2048x2048、2048x1152、3840x2160、および2160x3840。

ステップ4:制御された並行性を使用し、無制限のループは避ける

並行性とは、スクリプトが同時に処理する製品の数を意味します。ワーカーはレジの列のようなものだと考えてください。1人のワーカーが1つのアクティブな製品を処理し、3人のワーカーなら最大3つの製品を同時にシステム内で処理できます。

何百ものワーカーを起動するループは、レート制限に達したり、メモリを消費したり、障害からの復旧を難しくする可能性があります。最初は2~3で始めてください。アカウントの動作と完了時間を確認してから初めて増やしてください。

from concurrent.futures import ThreadPoolExecutor, as_completed

with ThreadPoolExecutor(max_workers=3) as pool:
    futures = [pool.submit(process_product, row) for row in products]
    for future in as_completed(futures):
        print(future.result())

Pythonのスレッドについて理解していなくても、この例は使えます。重要な設定は max_workers=3 です。429 エラーが繰り返し発生する場合は、2 または 1 に減らしてください。長時間のテストでレート制限エラーが出なければ、慎重により大きな値を試せます。

モデルプロバイダーが公開しているレート制限は、自動的に仲介APIアカウントの制限になるわけではありません。アクティブなGPT Protoキーにとっての信頼できる情報源として、429 Too Many Requests とその Retry-After ヘッダーを扱ってください。コードに攻撃的な数値を埋め込むのではなく、並行性を設定可能にしておきましょう。

OpenAIも直接モデルにアクセスする場合の階層ベースの制限を公開しています。これらの数値は参考情報として有用ですが、GPT Protoのレート制限として提示すべきではありません。

ステップ5:URLとBase64の結果を正しく保存する

GPT Protoはこのエンドポイントで response_format="url" と response_format="b64_json" をサポートしています。

初心者はまず url を使用してください。APIは通常の画像リンクを返し、スクリプトはファイルを generated フォルダにダウンロードします。結果URLは配信用リンクであり、永続的なアセットストレージではないため、すぐにダウンロードしてください。承認済みファイルは自社の製品メディアフォルダ、DAM、またはオブジェクトストレージに保存してください。

Base64は同じ画像を配信する別の方法です。応答にはURLの代わりに非常に長いテキスト文字列が含まれます。システムがすべてのデータをJSON内に保持する必要がある場合に便利ですが、ファイルを開く前にテキストをバイナリ画像バイトに変換し直す必要があります。このワークフローの最初のバージョンではBase64は必要ありません。

Base64出力の場合、文字列をバイトにデコードします:

import base64
from pathlib import Path

def save_base64_image(value, destination):
    # Also accepts a data URI such as data:image/png;base64,...
    encoded = value.split(",", 1)[1] if value.startswith("data:") else value
    Path(destination).write_bytes(base64.b64decode(encoded, validate=True))

GPT Image 2のBase64デコードでよくあるバグは、エンコードされたテキストを直接 .png ファイルに書き込んでしまうことです。有効な画像ファイルには、APIから返されたJSON文字列ではなく、デコードされたバイナリバイトが含まれています。

GPT Protoの代わりにOpenAIを直接使用する場合、GPT画像モデルは b64_json を返すことに注意してください。OpenAIの image-generation guide では、GPT画像モデルではURL応答形式がサポートされていないと述べられています。プロバイダー固有の違いがあるため、応答の解析は実際に呼び出すエンドポイントに合わせる必要があります。

ステップ6:重複画像を作成せずに再試行する

APIリクエストは、アカウントが作業を送信する速度が速すぎる、ネットワークが切断される、または生成自体が拒否されるなどの理由で失敗することがあります。失敗したコマンドをすべて繰り返したくなりますが、それは重複画像を作成する可能性があります。

再試行はすべてのリクエストで同じように安全なわけではありません。POST は新しいタスクを作成し、GET は既存のタスクを確認するだけです。

  • GET のステータスクエリは、別の画像を作成しないため、再試行しても安全です。

  • POST が明確な 429 拒否を受け取った場合、Retry-After を待って再試行できます。

  • POST の接続タイムアウトは曖昧です。クライアントが応答を受け取れなかったとしても、サーバーがジョブを受け入れた可能性があります。やみくもに繰り返すと、重複が作成され、課金される可能性があります。

  • 完了したジョブ、失敗したジョブ、またはモデレーションでブロックされたジョブを自動的に再送信しないでください。

以下のスクリプトは、送信直後にタスクIDを manifest.json に書き込みます。次回の実行では、同じSKUを再度POSTするのではなく、その既存タスクをポーリングします。送信が不明なネットワーク状態で終了し、タスクIDがない場合、レコードは manual_check としてマークされます。再試行する前にアカウント履歴を確認してください。

安全な再試行には、ジッター付きの指数バックオフを使用してください。APIが Retry-After を送信した場合はそれに従い、遅延と試行回数に上限を設けてください。繰り返し失敗する場合は、無限ループではなくマニフェストに記録されるべきです。

ステップ7:image_generation_user_error を処理する

image_generation_user_error はカテゴリであり、完全な診断ではありません。入力またはリクエストに関連する何かが原因でリクエストを生成できなかったことを意味します。何を変更するかを決める前に、内部のエラーコードとメッセージを読んでください。

一般的な対処法は次のとおりです:

エラーシグナル 考えられる対処法
moderation_blocked またはポリシーの詳細 プロンプトと参照画像を確認してください。変更せずに再試行しないでください
無効なサイズ ローカル検証を実行し、寸法を修正してください
無効なパラメータまたはサポートされていない形式 ペイロードを現在のエンドポイントリファレンスと比較してください
認証失敗 (401) キーの値と認証ヘッダーを確認してください
権限または残高の失敗 (403) モデルへのアクセスと残高を確認してください。自動バックオフは使用しないでください
レート制限 (429) Retry-After に従い、並行性を下げ、上限付きで再試行してください
サーバーエラー (500) リクエストを記録し、重複リスクを理解している場合にのみ再試行してください

単語を削除してリクエストが通過するまでモデレーションエラーを「修正」しないでください。製品、使用目的、参照アセット、プロンプトがプロバイダーのポリシーに準拠していることを確認してください。

初心者は、ジョブが失敗したときに次の順序で対処してください:

  1. SKUをmanifest.json で見つけます。

  2. すぐに製品を再送信するのではなく、その error フィールドを読みます。

  3. メッセージがサイズまたはパラメータを指定している場合は、その値を修正します。

  4. 429 の場合は、IMAGE_WORKERS を下げ、待ってから続行します。

  5. ポリシーまたはモデレーションエラーの場合は、製品とプロンプトを手動で確認します。

  6. ステータスが manual_check の場合は、レコードを削除する前にタスク履歴を確認してください。元のリクエストがすでに存在している可能性があります。

ステップ8:チームで使いやすい生成マニフェストを維持する

マニフェストは、設定が必要な API 機能ではありません。スクリプトによってお使いのコンピューター上に作成される JSON ファイルです。生成ログのようなものだと考えてください。

チームやビジネスでは、画像フォルダだけでは十分ではありません。マニフェストは、各アセットを SKU、プロンプト、設定、タスク ID、ステータス、出力パス、エラーに結び付けます。

レコードの例:

{
  "MUG-001": {
    "sku": "MUG-001",
    "status": "completed",
    "task_id": "prediction-id",
    "quality": "low",
    "size": "1024x1024",
    "prompt": "カタログ用の商品写真を1枚作成...",
    "files": ["generated/MUG-001-01.png"],
    "error": null
  }
}

このレコードでは、completed は画像が生成され、ダウンロードされたことを意味します。task_id は API の受付番号です。files は画像が保存された場所をチームに示します。生成が失敗した場合、error には、その SKU を修正すべきか、再試行すべきか、レビューすべきかを判断するために必要な情報が含まれます。

これにより、レビュー担当者に安定した引き継ぎが可能になります。また、却下された項目だけを再生成したり、プロンプトのバージョンを比較したり、アセットをそれを作成したリクエストまで追跡したりできます。より大きなチームでは、同じフィールドをデータベースに保存し、レビュー担当者、承認ステータス、プロンプトのバージョン、キャンペーン、権利に関するメモを追加してください。

一括商品生成用の完全なPythonスクリプト

次のスクリプトはワークフローを統合します。現在のGPT Proto v3 text-to-imageエンドポイントとprediction-resultルートを使用し、SKUごとにn=1を送信し、デフォルトで3つのワーカーを使用し、manifest.jsonから既知のタスクを再開します。

このコードは基本的なAPIデモより長いのは、いくつかの安全対策を自動で行うためです:

  • リクエストが送信される前に画像の寸法をチェックします。

  • APIが商品を受け入れたらすぐにタスクIDを保存します。

  • 手動でGETコマンドを実行することなく、各画像を待機します。

  • URL結果をダウンロードし、Base64結果をデコードします。

  • 一度に処理される商品数を制限します。

  • 失敗を記録し、再起動後に既知のタスクを続行します。

初回実行時は、関数を変更せずにスクリプトをコピーしてください。必要なのは products.csv とAPIキーだけです。上部の設定—SIZE、QUALITY、WORKERS、および POLL_TIMEOUT—は後で調整できます。

import base64
import csv
import json
import os
import random
import re
import threading
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
from urllib.parse import urlparse

import requests

API_ROOT = "https://gptproto.com/api/v3"
SUBMIT_URL = f"{API_ROOT}/openai/gpt-image-2/text-to-image"
RESULT_URL = f"{API_ROOT}/predictions/{{task_id}}/result"

CSV_PATH = Path(os.getenv("PRODUCTS_CSV", "products.csv"))
OUTPUT_DIR = Path(os.getenv("IMAGE_OUTPUT_DIR", "generated"))
MANIFEST_PATH = Path(os.getenv("IMAGE_MANIFEST", "manifest.json"))

SIZE = os.getenv("IMAGE_SIZE", "1024x1024")
QUALITY = os.getenv("IMAGE_QUALITY", "low")
WORKERS = int(os.getenv("IMAGE_WORKERS", "3"))
POLL_TIMEOUT = int(os.getenv("IMAGE_POLL_TIMEOUT", "900"))

API_KEY = os.environ["GPTPROTO_API_KEY"]
HEADERS = {
    "Authorization": f"Bearer {API_KEY}",
    "Content-Type": "application/json",
}

manifest_lock = threading.Lock()


class SubmissionStateUnknown(RuntimeError):
    """The POST may have reached the server, but no task ID was received."""


def validate_size(size):
    if size == "auto":
        return
    if not re.fullmatch(r"\d+x\d+", size.lower()):
        raise ValueError("Size must look like 1024x1024 or be 'auto'")

    width, height = map(int, size.lower().split("x"))
    pixels = width * height
    if width % 16 or height % 16:
        raise ValueError("GPT Image 2 width and height must be multiples of 16")
    if max(width, height) > 3840:
        raise ValueError("The longest edge cannot exceed 3840 pixels")
    if max(width, height) / min(width, height) > 3:
        raise ValueError("Aspect ratio cannot exceed 3:1")
    if not 655_360 <= pixels <= 8_294_400:
        raise ValueError("Total pixels are outside the supported range")


def build_prompt(row):
    return f"""Create one catalog product photograph.
Product: {row['name']}
Color: {row['color']}
Material: {row['material']}
View: {row['angle']}
Background: {row['background']}

Keep the product centered with realistic proportions and material texture.
Use soft studio lighting, a natural contact shadow, and generous crop-safe space.
No text, logos, labels, people, or extra objects.
Also avoid: {row.get('avoid', 'none')}.
Return a single product image, not a collage or contact sheet."""


def load_manifest():
    if not MANIFEST_PATH.exists():
        return {}
    return json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))


manifest = load_manifest()


def save_record(sku, **changes):
    with manifest_lock:
        record = manifest.setdefault(sku, {"sku": sku})
        record.update(changes)
        temporary = MANIFEST_PATH.with_suffix(".json.tmp")
        temporary.write_text(
            json.dumps(manifest, indent=2, ensure_ascii=False),
            encoding="utf-8",
        )
        temporary.replace(MANIFEST_PATH)


def parse_json(response):
    try:
        return response.json()
    except ValueError:
        return {"raw_response": response.text[:1000]}


def api_error(response):
    payload = parse_json(response)
    return RuntimeError(
        f"HTTP {response.status_code}: {json.dumps(payload, ensure_ascii=False)}"
    )


def submit(prompt):
    payload = {
        "prompt": prompt,
        "n": 1,
        "quality": QUALITY,
        "size": SIZE,
        "enable_sync_mode": False,
        "response_format": "url",
    }

    # Retry only a definite 429 rejection. A connection failure is ambiguous:
    # the job may exist even though this client did not receive its ID.
    for attempt in range(5):
        try:
            response = requests.post(
                SUBMIT_URL,
                headers=HEADERS,
                json=payload,
                timeout=(10, 180),
            )
        except requests.RequestException as exc:
            raise SubmissionStateUnknown(str(exc)) from exc

        if response.status_code != 429:
            if response.status_code >= 400:
                raise api_error(response)
            body = parse_json(response)
            data = body.get("data", body)
            task_id = data.get("id")
            if not task_id:
                raise RuntimeError(f"Submission returned no data.id: {body}")
            return task_id

        if attempt == 4:
            raise api_error(response)
        retry_after = response.headers.get("Retry-After")
        delay = float(retry_after) if retry_after and retry_after.isdigit() else 2**attempt
        time.sleep(min(30, delay) + random.random())

    raise RuntimeError("Submission retry loop ended unexpectedly")


def get_result(task_id):
    deadline = time.monotonic() + POLL_TIMEOUT
    delay = 2.0

    while time.monotonic() < deadline:
        try:
            response = requests.get(
                RESULT_URL.format(task_id=task_id),
                headers=HEADERS,
                timeout=(10, 60),
            )
        except requests.RequestException:
            time.sleep(delay + random.random())
            delay = min(10, delay * 1.5)
            continue

        if response.status_code == 429 or response.status_code >= 500:
            retry_after = response.headers.get("Retry-After")
            wait = float(retry_after) if retry_after and retry_after.isdigit() else delay
            time.sleep(min(30, wait) + random.random())
            delay = min(10, delay * 1.5)
            continue
        if response.status_code >= 400:
            raise api_error(response)

        body = parse_json(response)
        data = body.get("data", body)
        status = str(data.get("status", "")).lower()

        if status in {"completed", "succeeded"}:
            outputs = data.get("outputs") or []
            if not outputs:
                raise RuntimeError(f"Completed task returned no outputs: {body}")
            return outputs
        if status in {"failed", "canceled", "cancelled"}:
            raise RuntimeError(json.dumps(data.get("error") or body, ensure_ascii=False))

        time.sleep(delay)
        delay = min(10, delay * 1.25)

    raise TimeoutError(f"Task {task_id} exceeded {POLL_TIMEOUT} seconds")


def filename_part(value):
    cleaned = re.sub(r"[^A-Za-z0-9._-]+", "-", value.strip())
    return cleaned.strip("-._") or "product"


def extension_from_response(response, url):
    content_type = response.headers.get("Content-Type", "").split(";", 1)[0]
    by_type = {"image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp"}
    if content_type in by_type:
        return by_type[content_type]
    suffix = Path(urlparse(url).path).suffix.lower()
    return suffix if suffix in {".png", ".jpg", ".jpeg", ".webp"} else ".png"


def save_output(output, sku, index):
    OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
    stem = f"{filename_part(sku)}-{index:02d}"

    if isinstance(output, dict):
        output = output.get("url") or output.get("b64_json") or output.get("data")
    if not isinstance(output, str):
        raise TypeError(f"Unsupported output value: {output!r}")

    if output.startswith(("http://", "https://")):
        response = requests.get(output, timeout=(10, 180))
        response.raise_for_status()
        destination = OUTPUT_DIR / f"{stem}{extension_from_response(response, output)}"
        destination.write_bytes(response.content)
        return str(destination)

    encoded = output.split(",", 1)[1] if output.startswith("data:") else output
    destination = OUTPUT_DIR / f"{stem}.png"
    destination.write_bytes(base64.b64decode(encoded, validate=True))
    return str(destination)


def process_product(row):
    sku = row["sku"].strip()
    prompt = build_prompt(row)
    previous = manifest.get(sku, {})

    if previous.get("status") == "completed":
        return sku, "skipped: already completed"
    if previous.get("status") == "manual_check" and not previous.get("task_id"):
        return sku, "skipped: verify task history before resubmitting"
    if previous.get("status") == "failed":
        return sku, "skipped: review the recorded error before retrying"

    task_id = previous.get("task_id")

    try:
        if not task_id:
            task_id = submit(prompt)
            save_record(
                sku,
                status="submitted",
                task_id=task_id,
                quality=QUALITY,
                size=SIZE,
                prompt=prompt,
                files=[],
                error=None,
            )

        save_record(sku, status="processing")
        outputs = get_result(task_id)
        files = [save_output(value, sku, i) for i, value in enumerate(outputs, 1)]
        save_record(sku, status="completed", files=files, error=None)
        return sku, f"completed: {', '.join(files)}"

    except SubmissionStateUnknown as exc:
        save_record(
            sku,
            status="manual_check",
            task_id=None,
            quality=QUALITY,
            size=SIZE,
            prompt=prompt,
            files=[],
            error=f"Submission state unknown: {exc}",
        )
        return sku, "manual check required"
    except TimeoutError as exc:
        # Keep the task ID. The next run will poll this job instead of submitting
        # the SKU again, so a slow generation does not become a duplicate.
        save_record(sku, status="poll_timeout", task_id=task_id, error=str(exc))
        return sku, f"poll timed out; resume task {task_id} on the next run"
    except Exception as exc:
        save_record(sku, status="failed", task_id=task_id, error=str(exc))
        return sku, f"failed: {exc}"


def main():
    validate_size(SIZE)
    if WORKERS < 1:
        raise ValueError("IMAGE_WORKERS must be at least 1")

    with CSV_PATH.open(newline="", encoding="utf-8-sig") as handle:
        products = list(csv.DictReader(handle))

    required = {"sku", "name", "color", "material", "angle", "background"}
    missing = required - set(products[0]) if products else required
    if missing:
        raise ValueError(f"CSV is missing columns: {sorted(missing)}")

    skus = [row["sku"].strip() for row in products]
    if not all(skus):
        raise ValueError("Every CSV row must have a non-empty SKU")
    if len(skus) != len(set(skus)):
        raise ValueError("CSV contains duplicate SKUs; make each SKU unique before running")

    with ThreadPoolExecutor(max_workers=WORKERS) as pool:
        futures = [pool.submit(process_product, row) for row in products]
        for future in as_completed(futures):
            sku, message = future.result()
            print(f"[{sku}] {message}")


if __name__ == "__main__":
    main()

スクリプトの実行方法:初心者向けウォークスルー

1. 2つの入力ファイルを確認する

あなたの gpt-image-bulk フォルダを開き、以下が含まれていることを確認してください:

  • bulk_product_images.py, 完全な Python コードが含まれています。

  • products.csv、ヘッダーと少なくとも1つの製品行が含まれています。

スクリプトを次の名前で保存しないでください: bulk_product_images.py.txt。エディタがファイル拡張子を非表示にしている場合は、続行する前にファイルの種類を確認してください。

2. そのフォルダでターミナルを開く

ターミナルは2つのファイルと同じフォルダを参照している必要があります。次を使用できます: cd に続けてフォルダパスを指定します。例:

cd path/to/gpt-image-bulk

現在のフォルダは次で確認できます: pwd (macOS/Linux の場合) または Get-Location (PowerShell の場合)。

3. API キーを設定し、次をインストールする requests

設定する: GPTPROTO_API_KEY をセットアップセクションに示されているコマンドで設定します。次に、まだインストールしていない場合は requests をインストールします:

python -m pip install requests

4. ファイルを実行する

macOS、Linux、およびほとんどの Python 環境では:

python bulk_product_images.py

Windows では、Python コマンドが次である場合にこれを使用します: py:

py bulk_product_images.py

最初の製品は完了するまでに時間がかかる場合があります。API が生成している間、ターミナルが必ずしもフリーズしているわけではありません。タスクが完了すると、次のような行が表示されます:

[MUG-001] completed: generated/MUG-001-01.png
[BAG-014] completed: generated/BAG-014-01.png
[LAMP-207] completed: generated/LAMP-207-01.png

フォルダには次のものが含まれるようになります: generated/manifest.json。生成された画像を開き、それぞれを対応する CSV 行と比較します。次に、 manifest.json をテキストエディタで開き、成功したすべての SKU に "status": "completed" があることを確認します。

5. 再実行したときの動作を理解する

スクリプトは次を読み取ります: manifest.json を、新しいリクエストを送信する前に読み取ります。完了した SKU はスキップされます。以前にポーリング中に停止したタスクは、タスク ID を保持し、再度チェックされます。次のようにマークされたレコード: manual_check または failed は自動的に送信されません。

これは意図的な動作です。「コマンドをもう一度実行する」が「成功した画像ごとに再び支払う」ことにならないようにします。失敗したレコードを再試行しても安全な場合は、その SKU のレコードだけを次から削除します: manifest.json エラーを確認した後で。カタログ全体を意図的に再送信したい場合を除き、マニフェスト全体を削除しないでください。

6. 下書きから最終画像へ移行する

最初のパスでは、次を維持します: IMAGE_QUALITY=low および IMAGE_WORKERS=2 または 3。下書きを確認し、承認された製品行だけを次の名前の新しいファイルにコピーします: products-final.csv。

macOS または Linux で別の最終実行を行うには、下書きファイルが変更されないように、新しい出力名とマニフェスト名を使用します:

PRODUCTS_CSV=products-final.csv \
IMAGE_OUTPUT_DIR=generated-final \
IMAGE_MANIFEST=manifest-final.json \
IMAGE_QUALITY=high \
python bulk_product_images.py

Windows PowerShell では、最初に同じ値を設定してからスクリプトを実行します:

$env:PRODUCTS_CSV="products-final.csv"
$env:IMAGE_OUTPUT_DIR="generated-final"
$env:IMAGE_MANIFEST="manifest-final.json"
$env:IMAGE_QUALITY="high"
py bulk_product_images.py

これにより、最終画像が次の場所に生成されます: generated-final/ 下書き画像と下書きマニフェストは比較用に保持されます。

バルクジョブ向け GPT Image 2 の料金

GPT Image 2 の料金はトークンベースであるため、すべてのジョブに当てはまる正直な単一の「製品画像あたりのコスト」はありません。テキスト入力、参照画像入力、出力サイズ、品質、および結果の数がすべて合計に影響します。

初心者にとって、「100万トークンあたり」は課金単位であり、月額サブスクリプションでも、事前に100万トークンを購入する必要があるという意味でもありません。プロンプトはテキスト入力トークンになります。編集に使用する参照製品写真は画像入力トークンになります。生成された画像は画像出力トークンになります。テキストから画像へのリクエストでは、ソース画像がアップロードされないため、参照画像入力の料金は発生しません。

執筆時点では、GPT Proto のモデルページに 100 万トークンあたりの次の料金が記載されています:

トークンカテゴリ GPT Proto 掲載料金 OpenAI 標準料金
テキスト入力 $4.00 $5.00
画像入力 $6.40 $8.00
キャッシュ済み画像入力 $1.60 $2.00
画像出力 $24.00 $30.00

これは、記載されている OpenAI 標準トークン料金より 20% 低い価格です。価格は変更される可能性があるため、次を確認してください: ライブの GPT Image 2 モデルページ 見積もりを公開したり、大規模な実行を開始したりする前に。

OpenAI の料金リファレンスには、一般的なサイズの出力のみの有用な例も記載されています。 1024x1024 画像は、低品質で約 $0.006、中品質で $0.053、高品質で $0.211 と記載されています。 1536x1024 または 1024x1536 画像はそれぞれ約 $0.005、$0.041、$0.165 です。入力トークンは別途必要です。現在の OpenAI API 料金ページ を参照し、これらの数値を永続的なものとして扱わないでください。

カタログの見積もりには、実際の製品構成を反映した 20 件のパイロットを実行します。実際の支出、承認画像率、再生成率を記録します。次に計算します:

推定カタログコスト =
  試行あたりの平均 API コスト
  × 承認画像あたりの平均試行回数
  × 製品数

再生成率は見落としがちです。4 回の試行が必要な安価な下書きは、1 回目か 2 回目で承認される、より制御された中品質のジョブよりもコストがかかる場合があります。

テキストから画像ではなく参照画像を使用する場合

純粋なテキストから画像は、製品が一般的である場合、画像が概念的な場合、または正確なパッケージが販売可能なアイテムと一致する必要がない場合に適しています。正確な SKU 表現のデフォルトにするべきではありません。

既存の製品の形状、色、パッケージ、またはデザイン言語を保持する必要がある場合は、GPT Image 2 の画像編集エンドポイントを使用します。GPT Image 2 は、公式フォーマットのドキュメントによると、それぞれ 50 MB 未満の最大 16 枚の PNG、WebP、または JPEG 参照画像を受け入れ、自動的に高い入力忠実度を使用します。GPT Proto のプロバイダーフォーマットエンドポイントは、画像 URL または Base64 データ URI を受け入れます。次を参照してください: GPT Image 2 画像編集リファレンス。

適切な参照セットには次が含まれます:

  • きれいな正面または斜め前からの製品写真 1 枚。

  • 形状が曖昧な場合の 2 つ目の角度。

  • 重要な素材の質感やコントロールのクローズアップ。

  • プロンプトで明確に識別された、照明と背景用の別のスタイル参照。

参照画像は制御を改善しますが、ピクセルパーフェクトなラベル、小さな文字、または製品寸法を保証するものではありません。公開前に、人間のレビュアーが生成されたアセットとソース製品を比較する必要があります。

ビジネスとチーム利用のための本番チェックリスト

パイロットから一括生成に移行する前に:

  • プロンプトのバージョンを固定し、マニフェストに保存する。

  • 主要アセットキーとして、不変のSKUを1つ使用する。

  • 下書きは低品質で生成し、承認済みの構図のみを最終設定でレンダリングする。

  • ワーカー数を設定可能にし、繰り返し429応答が返されたら減らす。

  • ポーリング前にタスクIDを保存する。

  • 一時的な結果URLはすべて直ちにダウンロードする。

  • 分離する:failed、manual_check、およびcompleted 状態。

  • モデレーション、権限、残高のエラーをそのまま再試行しない。

  • サンプル結果の形状、色、ラベル、影、クロップ、重複オブジェクトを確認する。

  • ソース/参照の権利を保持し、レビュー承認を記録する。

  • APIキーはCSVやリポジトリではなく、シークレットマネージャーまたは環境変数に保管する。

カタログを自動化する前のプロンプト開発では、GPT Image 2 プロンプト例が視覚的な方向性を確立するのに役立ちます。選んだ方向性は、大規模に実行する前に構造化フィールドに変換してください。

小さなカタログスライスから始める

API呼び出しは簡単な部分です。信頼できる一括生成は、1つの商品を1つのバリエーションから分離し、同時実行数を制限し、入力を検証し、予測IDを保存し、すべての実行を再開可能にすることから生まれます。

まず低品質で代表的なSKUを10~20個から始めます。承認率と再生成率を測定し、共有プロンプトを修正してから、キューを拡張してください。現在のパラメータと価格は GPT Image 2モデルページ で確認できます。または、スクリプトを実行する準備ができたら GPT Protoホームページ からアカウントを作成できます。

よくある質問

GPT Image 2 API で複数の商品画像を作成するにはどうすればよいですか?

商品行ごとに1リクエストを使用し、通常は n=1 にして、それらのリクエストを制御されたワーカープールで処理します。返された各タスク ID を保存し、ダウンロードした出力をソース SKU にマッピングします。n>1 は、同じプロンプトの複数のバリエーションが必要な場合にのみ使用します。

ChatGPT に一度に複数の画像を生成してもらえますか?

会話中に ChatGPT に複数の画像やバリエーションを依頼できますが、このインターフェースは手動のクリエイティブ作業に最適です。CSV 入力、安定したファイル名、再試行、承認状態、ストア連携が必要な場合は、API を使用してください。

GPT Image 2 のレート制限はどれくらいですか?

プロバイダー、アカウント、現在の制限によって異なります。OpenAI は直接アクセス向けにティアベースの制限を公開していますが、それらの値を GPTProto アカウントにそのまま当てはめるべきではありません。まず2~3のワーカーで開始し、429 応答と Retry-After を確認して、実際の動作に基づいて調整してください。

GPT Image 2 でサイズを16の倍数にする必要があるのはなぜですか?

カスタムの幅と高さは、それぞれ16で割り切れる必要があります。また、サイズは辺、アスペクト比、総ピクセル数の制限内に収まる必要があります。リクエストを送信する前にローカルで検証してください。ストアが無効な寸法を要求する場合は、近い有効サイズで生成してから後でリサイズしてください。

GPT Image 2 の Base64 結果をデコードするにはどうすればよいですか?

存在する場合は data-URI プレフィックスを削除し、残りの文字列を base64.b64decode に渡して、結果のバイト列を画像ファイルに書き込みます。エンコードされた JSON 文字列を画像拡張子を付けて直接保存しないでください。

GPT Image 2 API の一括生成はチームに適していますか?

はい、パイプラインが生成だけでなくそれ以上の工程を含む場合に適しています。チームにはマニフェスト、不変の SKU マッピング、プロンプトのバージョン、レビュアーの状態、コスト追跡、あいまいな送信に対するルールが必要です。これらの管理により、引き継ぎや部分的な再実行が実用的になります。

商品画像に OpenAI Batch API を使用すべきですか?

最大24時間待機でき、その JSONL プロセスに適合する直接の OpenAI ワークロードには使用してください。別のレートプールと割引価格が提供されます。このガイドの GPTProto エンドポイントでは、GPTProto が Batch API サポートを明示的に文書化していない限り、記載されている prediction ワークフローを使用してください。
2026年最安のAI画像生成ツール6選:画像1枚あたりの実際のコスト

2026年最安のAI画像生成ツール6選:画像1枚あたりの実際のコスト

最も安いAI画像生成ツールは、何を購入したいかによって決まります。個人的な画像を3~4枚欲しいだけなら、無料のブラウザツールが有料APIよりコストが低くなります。しかし、アプリ内で何百枚もの画像が必要な場合、無料枠の変更や手動ダウンロードは実用的なコストモデルではありません。 API生成では、 GPT Image 1 Miniはこの比較で最も低い開始コストで、低品質の1024×1024画像あたり約$0.0035 、さらにわずかなテキスト入力料金がかかります。その請求額は品質、サイズ、トークン使用量によって変わります。 Nano Banana 2 Liteは、1K画像あたり$0.0202で最も安い予測可能な定額オプションです。 この区別は重要です。一方は可能な限り最も低い導入価格であり、もう一方はスタートアップの予算に組み込みやすいものです。以下の価格はすべて2026年9月3日に確認され、6つすべてのモデルはGPTProtoの AI画像生成ツール から利用できます。 今すぐ画像を生成

Schuyler Stacy | 2026-09-03

5 Best Midjourney API Alternatives in 2026: Real Model APIs, Not Discord Wrappers

5 Best Midjourney API Alternatives in 2026: Real Model APIs, Not Discord Wrappers

Midjourney can produce an excellent image from a loose creative brief. It is far less convenient when that image needs to sit inside an application, automated campaign system, product catalog, or customer-facing workflow. The reason is straightforward: Midjourney does not provide a standard public API for general developer use. Its current community guidelines also prohibit unauthorized automation of the service. Try Midjourney on GPTProto That leaves developers with two different routes: Use a third-party service that submits jobs to Midjourney on your behalf. Replace Midjourney with another image model that has a documented API. This guide covers the second route. The five options below are alternative image models—not Discord bots, browser automation tools, or unofficial Midjourney wrappers. The short answer: Seedream 5.0 Pro is the best overall Midjourney API alternative for polished visual concepts. Nano Banana Pro is the safer choice for complex commercial graphics, GPT Image 2 is better for exact instructions and text, FLUX.1 Kontext Max is worth considering for iterative editing, and Qwen Image Plus is the affordable option for bulk drafts. If you need Midjourney itself rather than a different model, compare the current third-party Midjourney API providers or open the Midjourney API page on GPTProto .

Tiffany Layne | 2026-09-01

Which AI Image Model Is Best for E-Commerce in 2026? 6 Models Tested on Product Photos

Which AI Image Model Is Best for E-Commerce in 2026? 6 Models Tested on Product Photos

One attractive sample tells you almost nothing about whether an AI image model is safe for e-commerce. A beautiful product photo can still contain the wrong bottle cap, a redesigned package, an invented feature, or—worse—the wrong price. We tested six image models with the same three e-commerce tasks: a nail-polish product-in-use photo, a floor lamp placed in a living room, and a wireless-earbuds ad with fixed copy. We tracked retries, product changes, physical mistakes, text accuracy, and whether the result could be published without repair. The short answer: Nano Banana Pro was the best AI image model for e-commerce overall. It was the only model that stayed near the top across all three tests. GPT Image 2 was the best for ads with exact text , while Qwen Image Plus was the lowest-cost option for bulk drafts . Midjourney produced the strongest lifestyle image, but it was the least trustworthy when the product and sales copy had to remain exact. If you only need general image-editing recommendations, see our separate guide to the best image-editing AI models . This comparison focuses specifically on e-commerce production and the cost of getting an image that is actually ready to publish. Try Nano Banana Pro Now

2026-08-27

7 Best Third-Party Midjourney API Providers in 2026: Pricing, Risks, and Verified Options

7 Best Third-Party Midjourney API Providers in 2026: Pricing, Risks, and Verified Options

Midjourney still does not document a public, self-serve API for general developer access. That has not stopped a market of third-party Midjourney API providers from offering hosted endpoints, Discord-account wrappers, and hybrid setups. The problem is not finding a list. It is finding a list that is still current. Several search results continue to recommend products that have removed Midjourney, no longer expose a verifiable product page, or cannot be checked securely. Other comparisons put a hosted request next to a bring-your-own-account wrapper as if they were equivalent. They are not. Try Midjourney on GPTProto My ranking is straightforward: GPTProto is the best overall choice for a multi-model workflow. It puts a versionless Midjourney route beside 200+ other text, image, video, and audio models under one key and shared balance. At about $0.06 per run , however, it is not the lowest listed price here. APIFrame is the strongest dedicated Midjourney workflow option. Its published range is $0.02–$0.10 per job , with four outputs per job, but its Basic plan starts at $39 per month . TTAPI is the price pick. Its listed Imagine rates start around $0.03 in Relax mode , rising to $0.05 Fast and $0.07 Turbo . One warning matters more than any ranking. Midjourney's Terms of Service , effective May 27, 2026, prohibit automated tools that access or generate through the service and prohibit reselling or redistributing access. Every provider in this list is unofficial. A working endpoint today does not remove account, policy, or service-continuity risk tomorrow.

Schuyler Stacy | 2026-08-25