# Gemini 3 Pro Image Batch API Discount: Save 50% and Retrieve Your Images

> The official Batch API cuts Gemini 3 Pro Image output prices to $0.067 for 1K/2K and $0.12 for 4K, before input and thinking charges. Use it for nonurgent work, budget for all billed tokens, and match downloaded images to their request keys.

- URL: https://blog.laozhang.ai/en/posts/gemini-3-pro-image-batch-api-discount
- Published: 2026-02-24
- Updated: 2026-10-07
- Author: LaoZhang AI Team (https://blog.laozhang.ai/en/about)
- Topic: API Pricing & Plans
- Tags: Gemini 3 Pro Image, Batch API, API Discount, Image Generation, Cost Optimization, Google AI

---
**The Gemini 3 Pro Image Batch API offers a 50% discount against equivalent Standard API processing.** Google's displayed image-output prices are $0.067 for 1K/2K and $0.12 for 4K. Input, reference images, and text/thinking output add to the bill. Batch is a good fit when images can arrive asynchronously; Google's target turnaround is 24 hours, rather than a guaranteed deadline. These are the [Gemini Developer API's current prices](https://ai.google.dev/gemini-api/docs/pricing#gemini-3-pro-image) and [Batch terms](https://ai.google.dev/gemini-api/docs/batch-api), checked October 6, 2026.

For a scheduled catalog update or an illustration backlog, the practical workflow is: prepare requests with unique keys, submit once, save the returned job name, wait for a terminal state, then download and inspect each result. A completed job does not necessarily give you one usable image for every request. The examples below keep those two outcomes separate.

## Understanding the Gemini 3 Pro Image Batch API Discount

Batch changes how requests are scheduled and billed. It does not require you to switch to a cheaper image model. Nano Banana Pro's current stable model ID is `gemini-3-pro-image`, and its [model page explicitly supports Batch](https://ai.google.dev/gemini-api/docs/models/gemini-3-pro-image). Some Batch documentation examples still use the older preview ID; the examples here use the current stable ID.

| Billed component | Standard | Batch |
| --- | --- | --- |
| 1K or 2K image output, Google's displayed equivalent | $0.134/image | $0.067/image |
| 4K image output | $0.24/image | $0.12/image |
| Text input | $2 per million tokens | $1 per million tokens |
| Reference-image input | Approximately $0.0011/image | Approximately $0.0006/image |
| Text and thinking output | $12 per million tokens | $6 per million tokens |

Source: [Google's Gemini 3 Pro Image pricing table and footnotes](https://ai.google.dev/gemini-api/docs/pricing#gemini-3-pro-image). Image-output amounts exclude the other components. Google lists 560 input tokens per reference image, 1,120 output tokens for 1K/2K, and 2,000 for 4K.

**Keep rounding consistent when planning volume.** Multiplying 1,120 image tokens by the Standard image rate of $120 per million gives $0.1344; halving it gives $0.0672 for Batch. Google's table displays $0.134 and $0.067. Both describe the same pricing schedule, but multiplying the rounded display figure by thousands of images slightly understates the token-based estimate. All volume calculations below use $0.1344/$0.0672 for 1K/2K and $0.24/$0.12 for 4K.

Moving from 4K to 2K reduces Batch image-output cost from $0.12 to $0.0672, a 44% reduction, if 2K meets your delivery requirements. Moving from 2K to 1K does not reduce this model's listed image-output charge. Choose resolution for the actual output you need.

Do not add a caching discount to this calculation. The [model capability table](https://ai.google.dev/gemini-api/docs/models/gemini-3-pro-image) says caching, Flex, and Priority are unsupported, although the generic pricing page includes Flex and Priority rows. A price row alone does not establish that the model accepts that processing option.

## Real Savings Calculator — Costs at Every Volume

For image output alone, the monthly saving is straightforward:

```text
Standard output cost = 1K/2K images × $0.1344 + 4K images × $0.24
Batch output cost    = 1K/2K images × $0.0672 + 4K images × $0.12
```

| Images per month | Standard, all 1K/2K | Batch, all 1K/2K | Batch, all 4K |
| --- | --- | --- | --- |
| 100 | $13.44 | $6.72 | $12.00 |
| 500 | $67.20 | $33.60 | $60.00 |
| 1,000 | $134.40 | $67.20 | $120.00 |
| 5,000 | $672.00 | $336.00 | $600.00 |
| 10,000 | $1,344.00 | $672.00 | $1,200.00 |
| 50,000 | $6,720.00 | $3,360.00 | $6,000.00 |

These are hypothetical output-only budgets at the [published rates](https://ai.google.dev/gemini-api/docs/pricing#gemini-3-pro-image), assuming the stated number of generated images. They exclude inputs, text/thinking, tools, revisions, taxes, and any additional billed attempts. They are not invoices or a promise that a project can generate that volume.

At 5,000 2K images per month, image output alone would cost $8,064 annually on Standard or $4,032 on Batch: $4,032 saved. For a mixed monthly workload of 800 2K and 200 4K images, Batch output would be `800 × $0.0672 + 200 × $0.12 = $77.76`.

### Add reference images and thinking to the estimate

A fuller Batch estimate is:

```text
Batch cost ≈ image-output cost
           + prompt text tokens × $1 / 1,000,000
           + reference-image count × 560 × $1 / 1,000,000
           + text/thinking output tokens × $6 / 1,000,000
           + applicable tool charges and other billed attempts
```

Reference-image cost uses Google's approximate token allocation. For example, assume 1,000 requests each generate one 2K image, contain 150 prompt tokens and two reference images, and produce 400 billed text/thinking tokens. The last number is a planning assumption, not a measured model average.

| Component | Calculation | Estimated Batch cost |
| --- | --- | --- |
| Image output | 1,000 × $0.0672 | $67.20 |
| Prompt text | 150,000 × $1 / 1,000,000 | $0.15 |
| Reference images | 2,000 × 560 × $1 / 1,000,000 | $1.12 |
| Text/thinking | 400,000 × $6 / 1,000,000 | $2.40 |
| Total, before tools and extra attempts | Sum of the above | $70.87 |

Under the same assumptions, Standard would cost $141.74. The model's [thinking process is always enabled](https://ai.google.dev/gemini-api/docs/image-generation#thinking-process); hiding its reasoning does not eliminate billed thinking tokens. Use returned usage information and settled billing records to replace assumptions with your workload's actual costs.

Also measure **cost per usable image**, not just cost per request. If that hypothetical $70.87 spend delivers 900 images that pass your checks, the cost is `$70.87 ÷ 900 ≈ $0.0787` per usable image, before any further work. A response containing text, an unusable image, or an item-level error belongs in your accounting even though it does not increase the usable-image count. The cited documentation does not establish a blanket refund rule for every failure or no-image outcome.

The examples do not enable Search grounding. The pricing table's Standard grounding terms should not be assumed to make every tool free in Batch; confirm the applicable tool charges before adding tools to a production job.

## Step-by-Step Batch Image Generation Guide

![The Batch workflow saves submission identity, resumes monitoring, and checks image bytes and missing keys after download.](https://blog.laozhang.ai/posts/en/gemini-3-pro-image-batch-api-discount/img/batch-workflow.webp)

Use the file workflow for larger image workloads. Google allows JSONL input files up to 2 GB and recommends them for image generation; inline requests are intended for smaller batches under 20 MB. Each file line needs a `key` and a valid `request` object. The key is how you associate the response with the original request. See [Google's input-file format](https://ai.google.dev/gemini-api/docs/batch-api#input-file) and [image Batch examples](https://ai.google.dev/gemini-api/docs/batch-api#image-generation).

The Python examples assume a paid Gemini Developer API project, authorized access to this model, and a current `google-genai` SDK. Configure credentials in your own environment; do not put keys in the request file. SDK-dependent code below is adapted from the documented interface. We checked Python syntax and the offline request/result helpers, but did not execute the SDK, submit a job, measure generation speed, or verify a live bill.

### 1. Prepare keyed JSONL requests

Save this as `make_requests.py`. It uses only Python's standard library and writes two sample requests to `requests.jsonl`.

```python
import json
from pathlib import Path

prompts = {
    "catalog-vase-front": "Studio product photo of a blue ceramic vase, front view, white backdrop",
    "catalog-vase-side": "Studio product photo of a blue ceramic vase, side view, white backdrop",
}

with Path("requests.jsonl").open("w", encoding="utf-8") as output:
    for key, prompt in prompts.items():
        row = {
            "key": key,
            "request": {
                "contents": [{"role": "user", "parts": [{"text": prompt}]}],
                "generationConfig": {
                    "responseModalities": ["TEXT", "IMAGE"],
                    "imageConfig": {"imageSize": "2K", "aspectRatio": "1:1"},
                },
            },
        }
        output.write(json.dumps(row) + "\n")
```

Run `python make_requests.py` locally, inspect the file, and keep it alongside the job record. Each key must be unique within the file and stable enough to identify the intended asset. These two independent requests do not promise consistent vase identity; reference images and an appropriate editing workflow are separate creative requirements.

For raw `GenerateContentRequest` JSON, use the camel-case fields above. Python SDK inline requests instead place settings in `config`, with `response_modalities` and `image_config`. Do not paste one shape into the other. `responseModalities` explicitly includes `IMAGE`; leaving modalities empty defaults to text. Gemini 3 Pro Image supports `1K`, `2K`, and `4K`; the generic [ImageConfig reference](https://ai.google.dev/api/generate-content#imageconfig) includes options for other models that should not be copied indiscriminately.

### 2. Upload once, submit once, and save the job name

Save this as `submit_batch.py`. It records the input filename, a unique display name, and the uploaded file name before calling create. On a returned response, it saves the batch name to `submission.json`.

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

record_path = Path("submission.json")
record = {
    "input": "requests.jsonl",
    "display_name": "catalog-" + uuid.uuid4().hex,
    "model": "gemini-3-pro-image",
    "phase": "prepared",
}

def save_record():
    temporary = record_path.with_suffix(".tmp")
    with temporary.open("w", encoding="utf-8") as handle:
        json.dump(record, handle, indent=2)
        handle.flush()
        os.fsync(handle.fileno())
    temporary.replace(record_path)

# Prevent an accidental rerun over an unresolved submission.
with record_path.open("x", encoding="utf-8") as handle:
    json.dump(record, handle, indent=2)

client = genai.Client()
uploaded = client.files.upload(
    file=record["input"],
    config=types.UploadFileConfig(
        display_name=record["display_name"], mime_type="jsonl"
    ),
)
record.update(uploaded_file=uploaded.name, phase="create_about_to_start")
save_record()

try:
    job = client.batches.create(
        model=record["model"],
        src=uploaded.name,
        config={"display_name": record["display_name"]},
    )
    print("Returned job name:", job.name, flush=True)
    record.update(job_name=job.name, phase="created")
    save_record()
except Exception:
    print("Submission needs reconciliation. Do not rerun create blindly.")
    raise
```

The upload MIME type is `jsonl`, and `src` is the uploaded file's **name string**, not the File object. These details follow the [documented file-based image workflow](https://ai.google.dev/gemini-api/docs/batch-api#image-generation).

Batch create is [not idempotent](https://ai.google.dev/gemini-api/docs/batch-api): submitting the same input twice creates two jobs. The local record prevents an ordinary rerun, but cannot make a network call and a filesystem write one atomic transaction. If create times out—or the process crashes before it saves the returned name—check the recent batch jobs and match the saved display name before deciding whether to submit again. If you find the job, save its name and resume it. A lost response is not evidence that no job exists.

### 3. Resume the saved job and download the result file

Save this as `collect_batch.py`. It reads the existing record; it never creates another job. A 30-minute local polling window keeps the process bounded. Run it again later with the same record if the job is still pending.

```python
import json
import time
from pathlib import Path
from google import genai

record = json.loads(Path("submission.json").read_text(encoding="utf-8"))
job_name = record["job_name"]
client = genai.Client()
terminal = {
    "JOB_STATE_SUCCEEDED", "JOB_STATE_FAILED",
    "JOB_STATE_CANCELLED", "JOB_STATE_EXPIRED",
}
deadline = time.monotonic() + 30 * 60

while True:
    job = client.batches.get(name=job_name)
    state = job.state.name
    print(job_name, state, flush=True)
    if state in terminal:
        break
    if time.monotonic() >= deadline:
        raise SystemExit("Still processing. Resume this job later; no cancellation sent.")
    time.sleep(min(60, max(0, deadline - time.monotonic())))

if state != "JOB_STATE_SUCCEEDED":
    raise SystemExit(f"Job ended as {state}; inspect its status/error before retrying.")
if not job.dest or not job.dest.file_name:
    raise SystemExit("No result file found; inspect the job destination.")

content = client.files.download(file=job.dest.file_name)
Path("results.jsonl").write_bytes(content)
print("Saved results.jsonl; inspect individual results next.")
```

The SDK exposes the file destination as `job.dest.file_name`; `client.files.download` returns bytes containing UTF-8 JSONL. This differs from an inline job's `job.dest.inlined_responses`, and from REST operation examples using `metadata`/`response` envelopes. Choose one interface throughout instead of mixing those field paths. The [official result-download example](https://ai.google.dev/gemini-api/docs/batch-api#image-generation) documents the file flow.

A polling or network error does not justify creating a replacement job. Keep `submission.json` and resume the named job once the connection is available. The local timeout stops waiting; it does not stop Google from processing or prove that charges have stopped.

### 4. Decode final image parts and account for missing results

Save this standard-library helper as `extract_images.py`. It reads the original keyed input and downloaded JSONL. It saves supported image types, skips thinking parts, records item errors and malformed image data, and reports keys without usable image bytes. It does not contact Google or create retries.

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

expected = {}
for line in Path("requests.jsonl").read_text(encoding="utf-8").splitlines():
    if line.strip():
        row = json.loads(line)
        if row["key"] in expected:
            raise ValueError("Duplicate input key")
        expected[row["key"]] = row

output = Path("batch-images")
output.mkdir(exist_ok=True)
extensions = {"image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp"}
seen, saved, issues = set(), {}, []

for number, line in enumerate(
    Path("results.jsonl").read_text(encoding="utf-8").splitlines(), 1
):
    if not line.strip():
        continue
    try:
        row = json.loads(line)
    except json.JSONDecodeError:
        issues.append({"line": number, "reason": "invalid_json"})
        continue
    key = row.get("key")
    if key not in expected or key in seen:
        issues.append({"line": number, "key": key, "reason": "unknown_or_duplicate_key"})
        continue
    seen.add(key)
    status = row.get("status") or {}
    status_error = bool(status) and (not isinstance(status, dict) or status.get("code", 0) != 0)
    if row.get("error") or status_error:
        issues.append({"key": key, "reason": "item_status", "detail": row})
        continue
    response = row.get("response") or {}
    paths = []
    for candidate in response.get("candidates") or []:
        for part in (candidate.get("content") or {}).get("parts") or []:
            if part.get("thought"):
                continue
            image = part.get("inlineData") or {}
            extension = extensions.get(image.get("mimeType"))
            if not extension or not image.get("data"):
                continue
            try:
                data = base64.b64decode(image["data"], validate=True)
            except (ValueError, TypeError, binascii.Error):
                issues.append({"key": key, "reason": "invalid_base64"})
                continue
            if not data:
                continue
            # Use numeric filenames so arbitrary keys cannot become paths.
            path = output / f"row-{number:06d}-{len(paths):02d}{extension}"
            path.write_bytes(data)
            paths.append(str(path))
    if paths:
        saved[key] = paths
    else:
        issues.append({"key": key, "reason": "no_final_image", "detail": response})

report = {
    "saved": saved,
    "missing_result_keys": sorted(set(expected) - seen),
    "keys_without_saved_images": sorted(set(expected) - set(saved)),
    "issues": issues,
}
Path("result-report.json").write_text(json.dumps(report, indent=2), encoding="utf-8")
print(f"Saved image bytes for {len(saved)} of {len(expected)} request keys.")
```

Run `python extract_images.py`, then inspect `result-report.json` and open the saved images. This checks that final image bytes were present; it is not a visual-quality check or a full image-file validator. A text-only response, safety block, corrupt file, or image that fails your creative requirements still needs attention.

The downloaded raw JSON contains Base64 in `inlineData.data`, which is decoded once here. SDK image parts are a different representation: for inline responses, Google's Python example uses `part.as_image()`. Do not Base64-decode an SDK byte array a second time. In either flow, match results by key, not by their position in a list.

## Advanced Batch Processing — Monitoring, Errors, and Optimization

![Deadline needs choose the delivery route, while job state and per-key image checks determine recovery.](https://blog.laozhang.ai/posts/en/gemini-3-pro-image-batch-api-discount/img/decision-tree.webp)

The job lifecycle and the asset lifecycle need separate tracking. The [documented terminal states](https://ai.google.dev/gemini-api/docs/batch-api#batch-job-status) are `SUCCEEDED`, `FAILED`, `CANCELLED`, and `EXPIRED`, with the `JOB_STATE_` prefix in code. A successful job can include item-level failures, and an item without an error can still lack a final image.

| What you observe | Next action |
| --- | --- |
| `PENDING` or `RUNNING` | Keep the job name; resume monitoring later. |
| `SUCCEEDED` | Download results, reconcile keys, save images, and inspect them. |
| One request has an error or no final image | Keep the original request and result; decide whether its cause warrants correction or retry. |
| A key is absent or duplicated | Reconcile the downloaded results before assuming an asset must be regenerated. |
| Create returned no reliable job name | Check recent jobs against the saved submission record before another create. |
| `FAILED`, `CANCELLED`, or `EXPIRED` | Inspect the job's details and billing context before planning replacement work. |

Google says jobs left pending or running expire after 48 hours without results, and successful results remain downloadable for six weeks. Retrieve them promptly and retain your own outputs. Cancellation stops processing new requests; it is not documented as a blanket reversal of all charges. See [Batch lifecycle and best practices](https://ai.google.dev/gemini-api/docs/batch-api).

Retry only the keys you have decided need another attempt. Preserve successful assets, fix invalid request settings rather than replaying them, and cap both attempts and spend. A batch-wide success counter cannot replace inspecting individual responses. For broader accounting when a timeout leaves charges unsettled, use a [spend guard that keeps uncertain costs reserved](https://blog.laozhang.ai/en/posts/llm-agent-api-spend-kill-switch).

For scale, the [Batch limit documentation](https://ai.google.dev/gemini-api/docs/rate-limits#batch-api-rate-limits) lists 100 concurrent jobs, a 2 GB input-file limit, 20 GB file storage, and model-specific queued-token limits shared across active jobs. Actual project limits can vary. The quota table's preview-model entry does not establish a guaranteed quota for the stable ID, and additional keys in the same project do not create independent project capacity.

Split work according to what you can monitor, retrieve, and recover within your deadline. There is no established universal image count at which Batch becomes worthwhile: two nonurgent requests qualify just as naturally as a catalog of thousands, while a single image needed during an interactive session may require Standard processing. Do not turn the 24-hour target into a next-day delivery guarantee.

## Frequently Asked Questions

### Does Batch produce lower-quality images than Standard?

Batch uses the same selected Gemini 3 Pro Image model, which [supports both image generation and Batch](https://ai.google.dev/gemini-api/docs/models/gemini-3-pro-image). The discount does not establish a different quality tier. It also does not promise pixel-identical outputs or a measured quality success rate; inspect your generated assets against the same requirements.

### Is the $0.067 Batch price the total cost of a 2K image?

No. It is Google's rounded image-output equivalent. The token-based figure is $0.0672, and input, reference images, text/thinking, tools, and additional billed attempts can increase the total. Google's [pricing table and footnotes](https://ai.google.dev/gemini-api/docs/pricing#gemini-3-pro-image) define the components. Your useful-image cost also depends on how many results you can actually use.

### Is there a free Batch tier for Gemini 3 Pro Image?

Google lists the Free Tier as unavailable for this model's Standard and Batch API pricing. Consumer-app access or an AI Studio experiment should not be budgeted as a recurring free API allowance. Check the [model-specific price table](https://ai.google.dev/gemini-api/docs/pricing#gemini-3-pro-image) for your intended API route.

### How long should I wait for Batch images?

Google targets 24 hours, with timing dependent on workload and service conditions. Jobs still pending or running after 48 hours expire. These are [documented Batch timing rules](https://ai.google.dev/gemini-api/docs/batch-api), not measured turnaround times from this guide. Keep a delivery buffer and use an interactive route when the user cannot wait.

### Can I submit a small batch without uploading a JSONL file?

Yes, [inline requests are supported for smaller batches under 20 MB](https://ai.google.dev/gemini-api/docs/batch-api#image-generation). In Python, use each request's SDK `config` and attach its identifying key through `metadata`, for example:

```python
inline_request = {
    "contents": [{"parts": [{"text": "A studio photo of a blue ceramic vase"}]}],
    "config": {
        "response_modalities": ["TEXT", "IMAGE"],
        "image_config": {"image_size": "2K", "aspect_ratio": "1:1"},
    },
    "metadata": {"key": "vase-inline-01"},
}
# With an already configured client, pass src=[inline_request] to batches.create.
# Save the returned job name as in the file workflow.
```

This is an input fragment, not a second complete submission script. Retrieve inline results through `job.dest.inlined_responses`, retain the returned metadata key, check errors, skip thinking parts, and save final image parts using the SDK's `part.as_image()` method. Use the file workflow when you want a persisted JSONL input and downloadable JSONL output.

## Sources

External pages this guide links to, in the order they appear. Last updated 2026-10-07.

- [Gemini Developer API's current prices](https://ai.google.dev/gemini-api/docs/pricing) (ai.google.dev)
- [Batch terms](https://ai.google.dev/gemini-api/docs/batch-api) (ai.google.dev)
- [model page explicitly supports Batch](https://ai.google.dev/gemini-api/docs/models/gemini-3-pro-image) (ai.google.dev)
- [thinking process is always enabled](https://ai.google.dev/gemini-api/docs/image-generation) (ai.google.dev)
- [ImageConfig reference](https://ai.google.dev/api/generate-content) (ai.google.dev)
- [Batch limit documentation](https://ai.google.dev/gemini-api/docs/rate-limits) (ai.google.dev)
