# Gemini 3 Pro ImageのBatch APIは半額：料金の見積もりと画像の回収手順

> Gemini 3 Pro Imageの公式Batch APIは、即時応答が不要な画像生成を通常料金の50%で処理します。画像出力だけなら1K・2Kは1枚0.067米ドル、4Kは0.12米ドル。入力・思考の費用も見積もり、要求キーとジョブIDを残して結果を取り分ける方法を紹介します。

- URL: https://blog.laozhang.ai/ja/posts/gemini-3-pro-image-batch-api-discount
- Published: 2026-10-06
- Updated: 2026-10-06
- Author: LaoZhang AI Team (https://blog.laozhang.ai/ja/about)
- Topic: API ガイド
- Tags: Gemini 3 Pro Image, Batch API, 画像生成, API料金

---
Gemini 3 Pro Imageで翌日分の商品画像や記事用の画像をまとめて作るなら、**公式Batch APIは通常料金の50%で使える選択肢です**。ユーザーを待たせる画面には通常の同期呼び出し、納期に余裕のある制作には非同期のBatch、という使い分けができます。Googleの目標処理時間は24時間で、完了時刻の保証ではありません。[公式Batch APIガイド](https://ai.google.dev/gemini-api/docs/batch-api)

画像出力の表示単価は1K・2Kが1枚0.067米ドル、4Kが0.12米ドルです。ただし、これは完成画像の総費用ではありません。入力テキスト、参照画像、出力テキストや思考トークンも含め、最後に使える画像が何枚残るかで見積もります。以下は2026年10月6日に確認した**Gemini Developer APIの有料料金**と、公式ドキュメントに基づく接続例です。Geminiアプリの月額プランやGoogle Cloudの別契約とは分けて考えてください。[料金表](https://ai.google.dev/gemini-api/docs/pricing#gemini-3-pro-image)

## 半額になる費用と、1,000枚の見積もり

対象モデルは `gemini-3-pro-image` です。現行のモデル情報で画像生成とBatchへの対応を確認できます。古いサンプルにあるpreview名をそのまま固定せず、利用時にはモデル情報を確認してください。[Gemini 3 Pro Imageの仕様](https://ai.google.dev/gemini-api/docs/models/gemini-3-pro-image)

| 課金対象 | 通常のStandard | 公式Batch |
| --- | ---: | ---: |
| テキスト・画像入力、100万トークンあたり | 2米ドル | 1米ドル |
| テキスト出力・思考、100万トークンあたり | 12米ドル | 6米ドル |
| 1K・2Kの画像出力、1枚あたりの表示単価 | 0.134米ドル | 0.067米ドル |
| 4Kの画像出力、1枚あたりの表示単価 | 0.24米ドル | 0.12米ドル |

この表は同じモデルの料金区分を比較したものです。1K・2Kの画像は1,120出力トークン、4Kは2,000出力トークンとして計算され、画像出力のトークン単価はStandardが100万トークンあたり120米ドル、Batchが60米ドルです。したがって、1K・2Kの計算上の単価はそれぞれ0.1344米ドル、0.0672米ドルになります。表示単価とトークン計算の丸め方を混ぜないことが大切です。[公式料金表](https://ai.google.dev/gemini-api/docs/pricing#gemini-3-pro-image)

たとえば、2K画像を1,000枚作り、各要求に入力テキスト200トークンと参照画像1枚を付けるとします。参照画像を公式の目安である560トークンと置き、すべての要求が画像を1枚返す仮定なら、次のように計算できます。

```text
画像出力：1,000 × 1,120 × 60 / 1,000,000 = 67.20米ドル
入力：1,000 × (200 + 560) × 1 / 1,000,000 = 0.76米ドル
小計：67.96米ドル
ここに出力テキスト・思考・利用した有料ツール等の費用を加える
```

同じトークン数をStandardで処理した場合、この小計は135.92米ドルです。実際の入力トークン数や思考量が違えば総額も変わります。参照画像のBatch表示単価0.0006米ドルも丸められた値なので、上の計算では560トークンから求めた0.00056米ドルを使っています。**思考は常時有効で、表示しない思考トークンにも料金がかかります**。[画像生成の思考に関する説明](https://ai.google.dev/gemini-api/docs/image-generation#thinking-process)

納品単価を出すときは、請求対象となった総費用を、検品後に採用できた画像数で割ります。仮に総費用が75米ドルで採用画像が900枚なら、1枚あたり約0.0833米ドルです。送信した1,000要求で割った0.075米ドルとは意味が違います。この数字は演算例で、実際の成功率や請求額を示すものではありません。

![画像出力と入力の小計に追加費用を加え、採用画像数から1枚あたりの費用を求める計算例](https://blog.laozhang.ai/posts/ja/gemini-3-pro-image-batch-api-discount/img/budget-example.webp)

## 納期と上限を確認してから投入する

Batchは、今すぐ画像を表示する必要がない処理に向いています。入稿期限が近い案件では、生成後の検品や必要な再生成にも時間を残してください。24時間は目標であり、「夜に投げれば朝に必ず揃う」とは約束できません。待機・実行中のまま48時間を超えると `JOB_STATE_EXPIRED` となり、結果を取得できません。[処理時間と状態](https://ai.google.dev/gemini-api/docs/batch-api#batch-job-status)

公式の共通上限は、同時100ジョブ、入力ファイル1個2GB、ファイル保存量20GBです。さらに、モデルごとのキュー内トークン上限があり、複数の実行中ジョブを合算します。実際のプロジェクトに割り当てられた上限を確認し、最初の投入単位を決めてください。APIキーを増やしても、同じプロジェクトの上限が別枠になるわけではありません。[Batchのレート制限](https://ai.google.dev/gemini-api/docs/rate-limits#batch-api-rate-limits)

少量なら20MB未満のインライン要求も使えますが、画像をまとめて回収する用途では**入力・出力ともJSONLファイルを使う方法**が扱いやすくなります。各行に独自の `key` を付け、順番に依存せず、商品や制作指示と結果を結び付けられるためです。[ファイル入力の仕様](https://ai.google.dev/gemini-api/docs/batch-api#input-file)

## キー付きJSONLで画像生成を登録する

ここでは2種類のマグカップの紹介画像を作る例を使います。次の内容をUTF-8の `requests.jsonl` として保存してください。**1要求を1行**にし、同じファイル内でキーが重複しないようにします。コード内の改行は、この2行の区切りだけです。

```jsonl
{"key":"mug-blue-01","request":{"contents":[{"role":"user","parts":[{"text":"青い陶器のマグカップを、白い背景で商品写真風に描いてください。文字やロゴは入れないでください。"}]}],"generationConfig":{"responseModalities":["TEXT","IMAGE"],"imageConfig":{"aspectRatio":"1:1","imageSize":"2K"}}}}
{"key":"mug-cream-01","request":{"contents":[{"role":"user","parts":[{"text":"クリーム色の陶器のマグカップを、白い背景で商品写真風に描いてください。文字やロゴは入れないでください。"}]}],"generationConfig":{"responseModalities":["TEXT","IMAGE"],"imageConfig":{"aspectRatio":"1:1","imageSize":"2K"}}}}
```

これはファイル内の生のGenerateContent JSONなので、`generationConfig`、`responseModalities`、`imageConfig` を使っています。Python SDKのインライン要求で使う `config`、`response_modalities`、`image_config` とは書式を混ぜないでください。画像を要求する `IMAGE` を指定しないと、テキストだけの出力になります。Proの画像サイズは1K・2K・4Kから選びます。汎用APIの設定に512があっても、Proで使えるサイズが増えるわけではありません。[生成設定の定義](https://ai.google.dev/api/generate-content#imageconfig)、[Batchの画像生成例](https://ai.google.dev/gemini-api/docs/batch-api#image-generation)

次のPythonコードを `batch_images.py` として保存します。実行にはGoogle Gen AI SDKと、利用者側で設定したGemini APIの認証情報が必要です。`submit` はファイルのアップロードと有料処理の登録を行い、`collect` は保存済みジョブの状態確認と結果のダウンロードを行います。

```python
import json
import os
import sys
import time
import uuid
from pathlib import Path
from google import genai
from google.genai import types

ROOT = Path("mug-batch")
ROOT.mkdir(exist_ok=True)
STATE = ROOT / "job.json"
TERMINAL = {
    "JOB_STATE_SUCCEEDED", "JOB_STATE_FAILED",
    "JOB_STATE_CANCELLED", "JOB_STATE_EXPIRED",
}

def save_state(data):
    tmp = ROOT / "job.tmp"
    with tmp.open("w", encoding="utf-8") as f:
        json.dump(data, f, ensure_ascii=False)
        f.flush()
        os.fsync(f.fileno())
    os.replace(tmp, STATE)

mode = sys.argv[1] if len(sys.argv) == 2 else ""
if mode not in {"submit", "collect"}:
    raise SystemExit("使い方: python batch_images.py submit | collect")
client = genai.Client()

if mode == "submit":
    if STATE.exists():
        raise SystemExit("job.jsonが既にあります。再送せず状態を確認してください。")
    source = Path("requests.jsonl")
    keys = [json.loads(line)["key"] for line in
            source.read_text(encoding="utf-8").splitlines() if line.strip()]
    if not keys or len(keys) != len(set(keys)):
        raise SystemExit("要求が空、またはキーが重複しています。")
    data = {"display_name": "mug-" + uuid.uuid4().hex, "keys": keys}
    save_state(data)  # 登録中に中断しても、無条件の再送を防ぐ
    uploaded = client.files.upload(
        file=str(source),
        config=types.UploadFileConfig(mime_type="jsonl"),
    )
    data["input_file"] = uploaded.name
    save_state(data)
    job = client.batches.create(
        model="gemini-3-pro-image",
        src=uploaded.name,
        config={"display_name": data["display_name"]},
    )
    data["job_name"] = job.name
    save_state(data)
    print(job.name)

else:
    data = json.loads(STATE.read_text(encoding="utf-8"))
    if not data.get("job_name"):
        raise SystemExit("登録結果が不明です。display_nameで既存ジョブを照合してください。")
    deadline = time.monotonic() + 1800
    while True:
        job = client.batches.get(name=data["job_name"])
        state = job.state.name
        print(state)
        if state in TERMINAL:
            break
        if time.monotonic() >= deadline:
            raise SystemExit("30分の待機を終了しました。後でcollectを再実行できます。")
        time.sleep(60)
    if state != "JOB_STATE_SUCCEEDED":
        raise SystemExit("終了状態を確認してください: " + state)
    if not job.dest or not job.dest.file_name:
        raise SystemExit("出力ファイルが見つかりません。ジョブ情報を確認してください。")
    raw = client.files.download(file=job.dest.file_name)
    (ROOT / "results.jsonl").write_bytes(raw)
    print("保存先:", ROOT / "results.jsonl")
```

初回は `python batch_images.py submit`、その後は `python batch_images.py collect` を使います。`src` に渡すのはアップロード結果のFileオブジェクトではなく、その `name` です。戻った `batches/...` というジョブ名を保存してから待機し、再開時も同じジョブを参照します。このコードの `job.state.name` と `job.dest.file_name` はPython SDKの形式です。RESTの応答全体に、そのまま同じ取り出し方を使わないでください。[登録と回収の公式例](https://ai.google.dev/gemini-api/docs/batch-api)

**ジョブの作成は冪等ではありません。** 同じ内容を2回登録すると、2つのジョブができます。作成直後に通信が切れた場合、ローカルにジョブ名がなくても、Google側では作成済みかもしれません。上のコードは記録ファイルを残して再送を止めますが、ネットワーク越しに一度だけ登録されることを保証するものではありません。SDKのジョブ一覧取得などで保存した `display_name` と入力ファイルを照合し、既存ジョブの `name` を `job.json` に補ってから `collect` へ戻ります。記録を消して `submit` をやり直す前に、この照合が必要です。[再送の注意点](https://ai.google.dev/gemini-api/docs/batch-api)

30分の待機終了やローカルの例外は、クラウドのジョブ取消しではありません。待機を終えた後も処理は続き得ます。上のコードは1つの作業ディレクトリを1プロセスで操作する例なので、同じディレクトリから同時に登録しないでください。本番では、入力ファイルとジョブ記録を永続ストレージで管理します。

## 結果を要求キーで照合して、画像を保存する

`JOB_STATE_SUCCEEDED` はジョブ全体の終了状態です。各要求が採用可能な画像を返したことまでは意味しません。ダウンロードしたJSONLには、応答を持つ行、エラーや状態を持つ行があり、画像の代わりにテキストだけ返る場合もあります。元の行番号ではなく `key` で照合します。成功した出力ファイルの取得期限は6週間なので、終了後に自分の保存先へ回収してください。[結果ファイルの取得](https://ai.google.dev/gemini-api/docs/batch-api#retrieving-results)

![要求キーと保存したジョブ名を使い、乱序の結果から画像と確認が必要な項目を取り分ける概念図](https://blog.laozhang.ai/posts/ja/gemini-3-pro-image-batch-api-discount/img/keyed-recovery.webp)

以下を `extract_images.py` として保存し、`python extract_images.py` で実行します。APIへの接続は行わず、先ほどの `mug-batch/results.jsonl` と `job.json` を読みます。生JSONの `inlineData.data` はBase64なので1回デコードします。SDKが既にバイト列として渡した画像を、さらにBase64デコードするコードではありません。

```python
import base64
import binascii
import json
from pathlib import Path

root = Path("mug-batch")
job = json.loads((root / "job.json").read_text(encoding="utf-8"))
expected = job["keys"]
index = {key: n for n, key in enumerate(expected, 1)}
out = root / "images"
out.mkdir(exist_ok=True)
extensions = {"image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp"}
seen, report, anomalies = set(), {}, []

for line_no, line in enumerate(
    (root / "results.jsonl").read_bytes().decode("utf-8").splitlines(), 1
):
    if not line.strip():
        continue
    try:
        row = json.loads(line)
    except json.JSONDecodeError:
        anomalies.append({"line": line_no, "reason": "invalid_json"})
        continue
    key = row.get("key")
    if key not in index or key in seen:
        anomalies.append({"line": line_no, "reason": "unknown_or_duplicate_key", "key": key})
        continue
    seen.add(key)
    response = row.get("response") or {}
    item = {"files": [], "text": [], "finish_reasons": [],
            "usage": response.get("usageMetadata"),
            "error": row.get("error"), "status": row.get("status")}
    for candidate in response.get("candidates", []):
        item["finish_reasons"].append(candidate.get("finishReason"))
        for part in (candidate.get("content") or {}).get("parts", []):
            if part.get("thought"):
                continue
            if "text" in part:
                item["text"].append(part["text"])
            blob = part.get("inlineData") or {}
            ext = extensions.get(blob.get("mimeType"))
            if not ext or not blob.get("data"):
                continue
            try:
                image = base64.b64decode(blob["data"], validate=True)
            except (binascii.Error, ValueError):
                anomalies.append({"key": key, "reason": "invalid_base64"})
                continue
            if not image:
                continue
            name = f"{index[key]:05d}-{len(item['files']) + 1}{ext}"
            (out / name).write_bytes(image)
            item["files"].append(name)
    item["result"] = "saved" if item["files"] else "no_image"
    report[key] = item

for key in expected:
    if key not in seen:
        report[key] = {"result": "missing", "files": []}
needs_check = [key for key, item in report.items()
               if item["result"] != "saved" or item.get("error") or item.get("status")]
(root / "report.json").write_text(
    json.dumps({"items": report, "needs_check": needs_check, "anomalies": anomalies},
               ensure_ascii=False, indent=2), encoding="utf-8"
)
print("画像保存済み要求:", sum(bool(item["files"]) for item in report.values()))
print("確認が必要なキー:", needs_check)
print("行・データの異常:", len(anomalies))
```

保存ファイル名は元の要求キーの順番を使った番号で、`report.json` に対応が残ります。戻ったキーを直接パスへ連結しないので、商品名にスラッシュが含まれていても保存先が変わりません。複数候補・複数画像は別ファイルになり、思考用の部分は最終画像として数えません。

`saved` は画像データをファイルに保存したという意味です。画像として正常に開けるか、指示どおりか、納品に使えるかは別途確認してください。`no_image`、`missing`、個別エラー、異常行をまず調べ、原因を直してから必要な要求だけを新しいキーで再生成します。既に保存できた画像は残し、ジョブ全体をもう一度送らないことが費用管理にも役立ちます。

この例では、JSONLの構文、Pythonの構文、順序を入れ替えた応答・個別エラー・画像なし・欠落・重複キーを含む架空データの解析、料金計算をオフラインで確認しました。SDKの接続、実モデルの画像生成、待ち時間や実請求の検証は行っていません。運用への組込み前に、利用中のSDKとプロジェクトで小さな有料ジョブを確認する必要があります。

## よくある疑問

### Batchなら無料枠でも画像を生成できますか？

Gemini 3 Pro ImageのAPI料金表には無料枠の提供が記載されていません。Batchの半額は、有料APIのStandard料金との比較です。Geminiアプリの利用回数や月額プランを、APIの無料枚数として数えないでください。[Gemini API料金表](https://ai.google.dev/gemini-api/docs/pricing#gemini-3-pro-image)

### キャッシュやFlexを併用すると、もっと安くなりますか？

2026年10月6日時点のモデル仕様では、Gemini 3 Pro Imageはコンテキストキャッシュ、Flex、Priorityに対応していません。汎用料金ページに項目があっても、このモデルで使える割引として加算しないでください。[モデル別の対応機能](https://ai.google.dev/gemini-api/docs/models/gemini-3-pro-image)

### 取消しや失敗なら、料金は必ずゼロですか？

ゼロとは判断できません。公式の取消し操作は新たな要求の処理を止めますが、確認したBatchガイドから、取消し・画像なし・個別失敗すべてに適用される無課金や返金の保証は読み取れません。ローカルのタイムアウトも請求確定の証拠にはなりません。ジョブ記録と利用明細を照合するまで、予算を解放しない設計が必要です。[公式Batchガイド](https://ai.google.dev/gemini-api/docs/batch-api)、[未確定費用を残すAPI予算管理](https://blog.laozhang.ai/ja/posts/llm-agent-api-spend-kill-switch)

### 公式Batchの割引は、他社のAPI経由でも適用されますか？

自動的には適用されません。この記事の半額はGoogleのGemini Developer APIで公式Batchを使う場合の料金です。別の提供会社を経由するなら、対応するモデルID、画像サイズ、非同期処理の有無、独自料金と課金条件を確認します。公式の割引と提供会社の料金を重ねて計算しないでください。[公式料金の対象](https://ai.google.dev/gemini-api/docs/pricing#gemini-3-pro-image)

## 参考資料

本文で参照している外部ページを、登場順に並べています。最終更新日：2026-10-06。

- [公式Batch APIガイド](https://ai.google.dev/gemini-api/docs/batch-api) (ai.google.dev)
- [料金表](https://ai.google.dev/gemini-api/docs/pricing) (ai.google.dev)
- [Gemini 3 Pro Imageの仕様](https://ai.google.dev/gemini-api/docs/models/gemini-3-pro-image) (ai.google.dev)
- [画像生成の思考に関する説明](https://ai.google.dev/gemini-api/docs/image-generation) (ai.google.dev)
- [Batchのレート制限](https://ai.google.dev/gemini-api/docs/rate-limits) (ai.google.dev)
- [生成設定の定義](https://ai.google.dev/api/generate-content) (ai.google.dev)
