Gemini 3 Pro Image Batch API: 반값 요금과 이미지 회수까지
Gemini 3 Pro Image의 Batch 요금은 같은 작업의 Standard 요금 대비 50%입니다. 다만 이미지 출력 단가가 최종 비용 전부는 아닙니다. 총비용 계산부터 JSONL 요청 작성, 이미지 저장, 실패 요청만 다시 처리하는 흐름까지 연결합니다.
목차

Gemini 3 Pro Image로 상품 이미지나 게시물용 이미지를 미리 만들어 두는 작업이라면, 공식 Batch API를 쓰는 것이 비용을 줄이는 한 가지 방법입니다. Batch 요금은 같은 작업의 Standard 요금보다 50% 낮습니다. 이미지 한 장을 즉시 돌려받는 대신 비동기로 결과를 받으며, Google은 24시간 이내 처리를 목표로 안내합니다. 작업량과 서비스 부하에 따라 달라지므로 특정 시각까지 완료된다는 보장은 아닙니다. 이 글의 가격과 기능은 2026년 10월 6일 확인한 Gemini Developer API 기준입니다. 공식 Batch 안내
지금 화면에서 결과를 기다리는 사용자에게는 일반 요청이 어울립니다. 다음 날 사용할 이미지를 백그라운드에서 렌더링하거나, 일정에 여유가 있는 대량 작업에는 Batch를 검토할 수 있습니다. Gemini 앱의 무료 생성 횟수나 소비자용 구독 플랜은 이 API 요금과 별개입니다.
현재 모델 ID는 gemini-3-pro-image입니다. 모델 사양은 Batch를 지원한다고 명시하지만 캐싱, Flex, Priority는 지원하지 않는다고 안내합니다. 따라서 다른 모델의 할인이나 캐시 절감률을 더해 이 모델의 비용을 계산하면 안 됩니다.
이미지당 0.067달러는 어떤 비용인가요?
공식 가격표의 1K·2K Batch 이미지 출력 단가는 이미지당 0.067달러, 4K는 0.12달러입니다. 이는 이미지 출력에 대한 요금이며, 텍스트 입력·참조 이미지 입력·텍스트 및 Thinking 출력은 따로 계산합니다. 아래 금액은 모두 미국 달러이며 세금이나 환율을 반영하지 않았습니다. Gemini 3 Pro Image 가격표
| 과금 항목 | Standard | Batch | 단위 |
|---|---|---|---|
| 텍스트·이미지 입력 토큰 | 2달러 | 1달러 | 백만 토큰 |
| 텍스트 및 Thinking 출력 토큰 | 12달러 | 6달러 | 백만 토큰 |
| 이미지 출력 토큰 | 120달러 | 60달러 | 백만 토큰 |
| 1K·2K 이미지 출력 | 0.134달러 | 0.067달러 | 이미지당, 표시 단가 |
| 4K 이미지 출력 | 0.24달러 | 0.12달러 | 이미지당 |
표의 마지막 두 행은 이미지 출력 토큰 요금을 이미지 한 장으로 환산한 값입니다. 토큰 요금과 이미지당 요금을 둘 다 더하지 않습니다. 1K·2K 이미지는 1,120개 출력 토큰 기준이므로 계산값은 Standard 0.1344달러, Batch 0.0672달러입니다. 가격표의 0.134·0.067달러는 반올림한 표시값입니다. 많은 장을 계산할 때는 한 가지 기준을 일관되게 사용하세요.
Thinking은 이 모델에서 항상 활성화되며, 화면에 표시하지 않아도 해당 토큰은 과금됩니다. Flash 모델의 Thinking 제어 옵션으로 Pro의 Thinking 비용을 없앨 수 있다고 가정하지 마세요. 이미지 생성의 Thinking 안내
2K 상품 이미지 2,000장 예산을 계산해 봅니다
다음은 실제 청구서가 아닌 예산 예제입니다. 각 요청이 최종 이미지 한 장을 생성하고, 요청당 텍스트 입력 300토큰과 참조 이미지 한 장 560토큰을 사용한다고 가정합니다. 참조 이미지 토큰 수는 가격표의 안내값을 적용했으며 실제 사용량은 응답과 청구 자료로 확인해야 합니다.
| 항목 | Batch 계산 | 예산 |
|---|---|---|
| 이미지 출력 | 2,000 × 1,120 × 60 ÷ 1,000,000 | 134.40달러 |
| 텍스트·참조 이미지 입력 | 2,000 × (300 + 560) × 1 ÷ 1,000,000 | 1.72달러 |
| 텍스트·Thinking 출력 | 전체 출력 토큰 수 × 6 ÷ 1,000,000 | 사용량에 따라 추가 |
텍스트·Thinking을 제외한 소계는 136.12달러입니다. 같은 가정의 Standard 소계는 272.24달러입니다. 여기에 실제 텍스트·Thinking 출력과, 사용한다면 별도로 적용되는 도구 비용을 더합니다. Batch 가격표에 특정 도구의 행이 없다는 이유로 도구 사용 전체가 무료라고 해석할 수는 없습니다.
예를 들어 전체 텍스트·Thinking 출력이 500,000토큰이었다면 Batch에 3달러를 더해 139.12달러가 됩니다. 이 수치는 가정한 토큰 수를 대입한 계산이며, 이미지당 고정 Thinking 양을 의미하지 않습니다.

예산을 검토할 때는 분모도 정해야 합니다. 총비용을 요청 수로 나누면 요청당 비용이고, 이미지가 나온 요청 수로 나누면 성공 요청당 비용입니다. 편집 검수까지 통과한 이미지 수로 나누어야 실제 사용 가능한 이미지당 비용이 됩니다. 예제의 139.12달러를 지출하고 쓸 수 있는 이미지가 1,800장이었다면 마지막 지표는 약 0.0773달러입니다. 실패·이미지 없음·취소의 일반적인 환불 규칙은 이 계산에서 가정하지 않았습니다.
요청별 key를 붙여 JSONL 파일을 만듭니다
이미지 작업은 JSONL 파일 방식으로 시작하면 결과를 저장하고 재처리 대상을 찾기 쉽습니다. 한 줄이 하나의 요청이며, 각 줄의 key는 원본 작업과 결과를 연결합니다. 아래 예제에서는 상품 번호와 이미지 용도를 묶어 mug-101-front, mug-102-front라는 고유 key를 사용합니다. 결과의 줄 순서로 상품을 연결하지 않습니다.
공식 안내는 작은 inline 입력을 20MB 미만으로 제한하며 이미지 생성에는 파일 입력을 권장합니다. 입력 파일은 최대 2GB입니다. 프로젝트별 Batch 제한에는 동시 작업 100개와 파일 저장 용량 20GB, 모델별 대기 토큰 제한도 있으므로 실제 프로젝트의 할당량을 확인한 뒤 작업을 나눕니다. 여러 API 키를 만든다고 같은 프로젝트의 제한이 늘어나는 것은 아닙니다. Batch 입력 안내, Batch 할당량
다음 코드를 prepare_requests.py에 저장하면 네트워크 호출 없이 catalog.jsonl이 만들어집니다. 이 파일 예제는 텍스트만 입력하므로 위 비용 예제의 참조 이미지 입력은 포함하지 않습니다.
import json
from pathlib import Path
products = [
("mug-101-front", "흰색 도자기 머그잔"),
("mug-102-front", "남색 도자기 머그잔"),
]
with Path("catalog.jsonl").open("x", encoding="utf-8") as out:
for key, product in products:
row = {
"key": key,
"request": {
"contents": [{
"role": "user",
"parts": [{"text": (
f"{product}의 정면 상품 사진을 만들어 주세요. "
"밝은 배경, 자연스러운 그림자, 글자 없이."
)}],
}],
"generationConfig": {
"responseModalities": ["TEXT", "IMAGE"],
"imageConfig": {
"aspectRatio": "1:1",
"imageSize": "2K",
},
},
},
}
out.write(json.dumps(row, ensure_ascii=False) + "\n")responseModalities에 IMAGE를 넣고 해상도는 1K, 2K, 4K 중에서 선택합니다. 범용 API 문서에 512가 보이더라도 이 Pro 모델의 지원 해상도로 간주하면 안 됩니다. 여기의 1:1은 해당 모델이 지원하는 비율입니다. GenerateContent의 이미지 설정, 모델별 사양
파일 안의 generationConfig·imageConfig는 원시 GenerateContent JSON 필드입니다. Python SDK의 inline 요청에서는 config.response_modalities 등 SDK 형식을 사용합니다. 두 입력 형식을 섞지 마세요. inline 결과는 job.dest.inlined_responses에서 가져오며, SDK 이미지 part에는 as_image()를 사용할 수 있습니다. 다음 과정은 파일 입력과 원시 JSONL 결과에만 맞춘 예제입니다.
파일을 제출하고 작업 이름을 즉시 저장합니다
아래는 인증이 구성된 환경과 google-genai 패키지가 필요한 공식 문서 기반 예제입니다. 파일 생성·결과 파싱·계산은 오프라인으로 확인했으며, 이 글을 작성하면서 SDK를 실행하거나 유료 이미지 요청을 보내지는 않았습니다. 실제 서비스에서는 설치된 SDK 버전과 프로젝트의 모델 접근 권한도 확인해야 합니다.
submit_batch.py로 저장합니다. batch-job.txt가 있으면 기존 작업을 조회하는 쪽으로 진행하고 새로 제출하지 않도록, 먼저 배타적으로 파일을 만듭니다.
from pathlib import Path
from google import genai
from google.genai import types
client = genai.Client()
job_path = Path("batch-job.txt")
# 같은 작업 디렉터리에서 제출을 다시 실행하지 않도록 예약합니다.
with job_path.open("x", encoding="utf-8") as saved:
saved.write("SUBMISSION_IN_PROGRESS\n")
saved.flush()
uploaded = client.files.upload(
file="catalog.jsonl",
config=types.UploadFileConfig(
display_name="catalog-ko",
mime_type="jsonl",
),
)
job = client.batches.create(
model="gemini-3-pro-image",
src=uploaded.name,
config={"display_name": "catalog-ko-20261006"},
)
saved.seek(0)
saved.truncate()
saved.write(job.name + "\n")
saved.flush()
print(job.name)src에는 업로드된 File 객체 전체가 아니라 uploaded.name 문자열을 전달합니다. 위 코드는 공식 파일 Batch 예제의 연결 관계를 따르되, 현재 모델 사양의 안정 버전 ID를 사용합니다. 파일 Batch 예제
Batch 생성은 멱등하지 않습니다. 같은 생성 요청을 두 번 보내면 작업이 두 개 생길 수 있습니다. 연결이 끊겼는데 SUBMISSION_IN_PROGRESS만 남았다면 파일을 삭제하고 곧바로 재실행하지 마세요. 저장한 로그와 프로젝트의 작업 목록에서 생성 여부를 대조하고 기존 작업 이름을 찾아야 합니다. 위 파일 예약은 로컬 재실행을 막는 장치일 뿐, 네트워크 호출과 로컬 저장을 하나의 원자적 트랜잭션으로 만들지는 못합니다. 운영 환경에서는 요청 파일의 해시, 제출 시각, 업로드 파일 이름과 작업 이름을 영속 저장소에 함께 남기는 편이 좋습니다. Batch 재시도 유의사항
제한 시간까지만 조회하고 결과 파일을 내려받습니다
PENDING·RUNNING은 대기 또는 실행 상태입니다. 종료 상태는 SUCCEEDED, FAILED, CANCELLED, EXPIRED이며 SDK에서는 JOB_STATE_ 접두사가 붙습니다. 48시간 동안 대기·실행 상태를 벗어나지 못해 만료되면 결과가 반환되지 않습니다. 성공한 결과는 기본적으로 6주 동안 다운로드할 수 있으므로 완료 후 자체 저장소로 옮기세요. 상태와 결과 보존 안내
다음 코드를 download_batch.py에 저장합니다. 예제의 30분은 로컬 프로세스가 한 번에 기다릴 시간이며 Google의 완료 약속이 아닙니다. 시간이 끝나면 같은 batch-job.txt로 나중에 다시 조회할 수 있습니다.
import time
from pathlib import Path
from google import genai
client = genai.Client()
job_name = Path("batch-job.txt").read_text(encoding="utf-8").strip()
if not job_name.startswith("batches/"):
raise RuntimeError("먼저 제출 결과와 실제 작업 이름을 확인하세요.")
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
if state in terminal:
break
remaining = deadline - time.monotonic()
if remaining <= 0:
raise TimeoutError(f"로컬 대기 종료. 나중에 같은 작업 조회: {job_name}")
time.sleep(min(60, remaining))
if state != "JOB_STATE_SUCCEEDED":
raise RuntimeError(f"종료 상태: {state}; 오류: {job.error}")
if not job.dest or not job.dest.file_name:
raise RuntimeError("성공 상태지만 결과 파일 정보가 없습니다.")
payload = client.files.download(file=job.dest.file_name)
Path("results.jsonl").write_bytes(payload)
print("results.jsonl 저장 완료")조회나 다운로드에서 오류가 나면 새 Batch를 만들지 말고 먼저 같은 작업 이름으로 재개합니다. 로컬 대기 시간 만료는 클라우드 작업 취소가 아닙니다. 실제 취소는 새 요청 처리를 중단하지만, 취소했다는 사실만으로 이미 진행한 모든 요청의 요금이 사라졌다고 판단할 수는 없습니다. 결과 다운로드, 작업 취소
성공한 작업에서도 요청별 오류와 이미지 없음을 구분합니다
Batch 전체가 성공해도 모든 요청에서 사용할 이미지가 나온다는 뜻은 아닙니다. 결과 파일의 각 줄을 원래 key에 연결하고, 개별 오류·텍스트만 있는 응답·누락된 이미지·잘못된 데이터는 따로 남겨야 합니다. Thinking part는 최종 이미지 수에 포함하지 않습니다.

다음 extract_images.py는 네트워크를 사용하지 않습니다. 원시 JSON의 inlineData.data는 Base64 문자열이므로 한 번 디코딩합니다. 이미 바이트인 SDK 결과를 다시 Base64 디코딩하는 방식과는 다릅니다. 이 파서는 key별로 결과를 모아 중복 key와 알 수 없는 key를 보류하고, 정상 이미지가 하나 이상인 요청을 저장합니다. 파일을 디코딩했다는 사실만으로 이미지의 품질이나 내용까지 검수한 것은 아닙니다.
import base64
import binascii
import json
from pathlib import Path
EXT = {"image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp"}
def parse_results(text, expected_keys):
grouped, issues = {}, []
for number, line in enumerate(text.splitlines(), 1):
if not line.strip():
continue
try:
row = json.loads(line)
if not isinstance(row, dict):
raise ValueError("object가 아닙니다")
except (ValueError, TypeError) as exc:
issues.append({"line": number, "reason": "invalid_json", "detail": str(exc)})
continue
key = row.get("key")
if not isinstance(key, str) or key not in expected_keys:
issues.append({"line": number, "reason": "unknown_key"})
continue
grouped.setdefault(key, []).append(row)
images = {}
for key in expected_keys:
rows = grouped.get(key, [])
if len(rows) != 1:
reason = "missing_result" if not rows else "duplicate_key"
issues.append({"key": key, "reason": reason})
continue
row = rows[0]
status = row.get("status") or {}
if row.get("error") or status.get("code", 0):
issues.append({"key": key, "reason": "request_error", "detail": row})
continue
response = row.get("response") or {}
found, invalid = [], False
for candidate in response.get("candidates") or []:
content = candidate.get("content") or {}
for part in content.get("parts") or []:
if part.get("thought"):
continue
data = part.get("inlineData") or {}
mime, encoded = data.get("mimeType"), data.get("data")
if not data:
continue
if mime not in EXT or not isinstance(encoded, str):
invalid = True
continue
try:
blob = base64.b64decode(encoded, validate=True)
except (binascii.Error, ValueError):
invalid = True
continue
if blob:
found.append((EXT[mime], blob))
else:
invalid = True
if found:
images[key] = found
if invalid or not found:
reason = "invalid_image_data" if invalid else "no_final_image"
issues.append({"key": key, "reason": reason})
return images, issues
if __name__ == "__main__":
requests = [json.loads(line) for line in Path("catalog.jsonl").read_text(
encoding="utf-8").splitlines() if line.strip()]
keys = [row["key"] for row in requests]
if len(keys) != len(set(keys)):
raise ValueError("입력 key가 중복됩니다.")
images, issues = parse_results(
Path("results.jsonl").read_bytes().decode("utf-8"), set(keys))
output = Path("saved-images")
output.mkdir(exist_ok=False)
manifest = {}
for key, parts in images.items():
# 입력 key를 파일 경로로 직접 쓰지 않습니다.
index = keys.index(key)
manifest[key] = []
for n, (extension, blob) in enumerate(parts, 1):
name = f"item-{index:06d}-{n}{extension}"
(output / name).write_bytes(blob)
manifest[key].append(name)
(output / "manifest.json").write_text(
json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8")
(output / "issues.json").write_text(
json.dumps(issues, ensure_ascii=False, indent=2), encoding="utf-8")순서는 python prepare_requests.py, python submit_batch.py, python download_batch.py, python extract_images.py입니다. 앞 단계가 끝난 뒤 다음 단계로 진행합니다. saved-images/manifest.json에서 상품 key와 저장 파일을 연결하고, 실제로 열어 사용 가능 여부를 검수합니다. 기존 saved-images 디렉터리가 있으면 덮어쓰지 않으므로, 원본 결과를 보존한 채 새로운 디렉터리에서 다시 추출하거나 저장 정책을 조정하세요. 공식 이미지 결과 구조
재시도할 요청을 고르기 전에 확인할 것
issues.json 전체를 그대로 새 Batch로 보내면 안 됩니다. unknown_key·duplicate_key·JSON 파싱 오류는 원본 파일과 저장 과정부터 확인해야 합니다. missing_result도 다운로드가 완전했는지 확인한 뒤 판단합니다. request_error는 오류 코드와 내용을 보고 입력 문제인지 일시적인 문제인지 구분합니다. 이미지가 없거나 데이터가 손상된 요청은 이미 정상 이미지가 저장되어 있는지도 확인합니다.
재처리가 필요하다고 결정한 key만 원본 catalog.jsonl에서 골라 별도 파일로 만들고, 새 작업 이름으로 제출합니다. 원래 key는 유지하되 작업 이름과 시도 횟수는 별도로 기록해야 이전 결과와 혼동하지 않습니다. 자동 재시도에는 횟수와 금액 상한을 두고, 이미지 검수가 끝난 요청은 제외합니다. 응답이 없거나 요청이 실패했다는 이유만으로 요금을 0으로 확정하지 마세요. 상위 서비스의 지출 통제까지 설계해야 한다면 공급자 호출 전에 지출을 막는 API 비용 킬 스위치를 이어서 참고할 수 있습니다.
이 예제는 결과 순서가 바뀐 경우, 개별 오류, 이미지 없음, Thinking part, 누락·중복 key, 잘못된 Base64 등을 합성 데이터로 확인했습니다. 실제 API 수락 여부, 생성 속도, 청구 금액, 이미지 품질을 측정한 테스트는 아닙니다. 글의 삽화도 설명용이며 해당 모델의 생성 품질 표본이 아닙니다.
자주 묻는 질문
Batch로 요청하면 이미지 품질도 50% 낮아지나요?
공식 할인은 처리 방식과 요금에 관한 안내입니다. 할인율을 품질 감소율로 바꿔 해석할 근거는 없지만, 같은 프롬프트가 픽셀 단위로 동일한 결과를 보장한다는 의미도 아닙니다. 실제 사용 기준으로 결과를 검수하세요. 공식 Batch 안내
2K 이미지 1,000장이면 총 67달러인가요?
67달러는 표시 단가 0.067달러로 계산한 이미지 출력 비용입니다. 1,120개 출력 토큰과 Batch 토큰 단가를 그대로 계산하면 67.20달러이며, 입력과 텍스트·Thinking 출력은 추가됩니다. 견적에는 적용한 반올림 기준도 남기세요. 모델 가격표
24시간을 넘기면 새 작업을 보내도 되나요?
먼저 기존 작업 이름으로 상태를 확인해야 합니다. 24시간은 처리 목표이지 자동 취소 시각이 아니며, 생성 요청을 반복하면 중복 작업이 생길 수 있습니다. 종료 상태와 기존 결과를 확인한 뒤 필요한 요청만 다시 제출하세요. Batch 처리 및 재시도 안내
캐싱을 함께 쓰면 더 할인되나요?
현재 gemini-3-pro-image의 모델 사양은 캐싱 미지원으로 안내합니다. 다른 모델의 캐시 요금을 이 모델의 Batch 할인과 합산해 추가 절감률을 제시할 수 없습니다. Flex와 Priority도 이 모델에서 지원되는 선택지로 안내하지 않습니다. 모델 기능표
참고 자료6
이 글이 링크한 외부 페이지를 본문에 나온 순서대로 정리했습니다. 마지막 업데이트: 2026년 10월 6일.
참고 자료6
이 글이 링크한 외부 페이지를 본문에 나온 순서대로 정리했습니다. 마지막 업데이트: 2026년 10월 6일.
- 1.공식 Batch 안내ai.google.dev/gemini-api/docs/batch-api
- 2.모델 사양ai.google.dev/gemini-api/docs/models/gemini-3-pro-image
- 3.Gemini 3 Pro Image 가격표ai.google.dev/gemini-api/docs/pricing
- 4.이미지 생성의 Thinking 안내ai.google.dev/gemini-api/docs/image-generation
- 5.Batch 할당량ai.google.dev/gemini-api/docs/rate-limits
- 6.GenerateContent의 이미지 설정ai.google.dev/api/generate-content





