GPT-6 Sol·Claude Opus 5.5 출시GPT-6 Sol 요금은 5.6 Sol의 절반

Nano Banana Pro API: Python 호출, 이미지 편집, 가격

POST 한 번, 폴링 한 번, 다운로드 한 번. 그대로 실행되는 스크립트입니다.

Kiki, OmniaKey COO약 9분
이 페이지 목차

Nano Banana Pro는 Google의 gemini-3-pro-image입니다. OmniaKey에서는 POST /v1/media/tasks 한 번으로 호출합니다. 3:4 비율로 세 장을 시험했고 결과는 896x1200, 1792x2400, 3584x4800이었으며 포스터의 글자는 하나도 틀리지 않았습니다.

먼저 결론

  • 모델: gemini-3-pro-image가 Google의 GA 모델 ID입니다. Vertex 모델 카드에는 출시일이 2026년 5월 28일로 적혀 있습니다.
  • 호출: POST https://api.omniakey.com/v1/media/tasks에 Idempotency-Key를 붙여 보내고, GET /v1/media/tasks/{id}를 succeeded가 될 때까지 폴링한 다음 assets[0].url을 내려받습니다.
  • 파라미터: parameters.aspect_ratio와 parameters.image_size입니다. 크기 값은 1K, 2K, 4K이며 K는 대문자입니다.
  • 요청 한도: 본문 전체 1 MiB이고 인라인 Base64 이미지도 포함됩니다.
  • Google 가격: 1K/2K 한 장에 $0.134, 4K 한 장에 $0.24이며 Google Gemini API 가격 페이지의 값입니다.
  • 실제 토큰 수: 세 작업은 출력 토큰을 1,133, 1,225, 2,480개로 보고했습니다. 이미지 토큰 100만 개당 $120으로 계산하면 $0.136, $0.147, $0.298입니다.
  • 편집: operation: "edit"을 쓰고 원본 이미지를 input.image에 {data, mime_type} 형태로 넣습니다.

OmniaKey의 Nano Banana Pro 모델 페이지에서는 예전 스냅샷 ID인 gemini-3-pro-image-preview도 같은 가격으로 됩니다.

모델 ID와 준비물

코드에는 gemini-3-pro-image를 씁니다. Google의 Vertex 모델 카드는 이 모델을 GA로 올리고 출시일을 2026년 5월 28일로 적었으며, 1K·2K·4K 출력을 지원합니다.

gemini-3-pro-image-preview는 같은 모델을 가리키는 ID입니다. OmniaKey의 이미지 모델 표에도 "Same model, -preview snapshot id"로 적혀 있고 호출당 요금도 같습니다.

준비할 것은 두 가지입니다.

  1. OmniaKey API 키를 환경 변수에 넣습니다: export OMNIAKEY_API_KEY="your-key".
  2. Python 3. 아래 스크립트는 표준 라이브러리만 씁니다. urllib, json, time, uuid, base64, pathlib이고 SDK를 설치할 필요가 없습니다.

Python 스크립트 실행하기

단계는 세 가지입니다. 작업을 만들고, 상태를 폴링하고, 이미지를 내려받습니다. nano_banana.py로 저장하고 python3 nano_banana.py로 실행하십시오.

python
import base64
import json
import os
import time
import urllib.request
import uuid
from pathlib import Path

API = "https://api.omniakey.com/v1/media/tasks"
HEADERS = {
    "Authorization": f"Bearer {os.environ['OMNIAKEY_API_KEY']}",
    "Content-Type": "application/json",
}


def call(url, payload=None, headers=None, method=None):
    body = json.dumps(payload).encode() if payload is not None else None
    req = urllib.request.Request(url, data=body, headers=headers or HEADERS, method=method)
    with urllib.request.urlopen(req, timeout=60) as resp:
        return resp.status, json.loads(resp.read())


def create_task(prompt, aspect_ratio="3:4", image_size="2K", image_path=None):
    body = {
        "model": "gemini-3-pro-image",
        "type": "image",
        "operation": "generate" if image_path is None else "edit",
        "input": {"prompt": prompt},
        "parameters": {"aspect_ratio": aspect_ratio, "image_size": image_size},
    }
    if image_path:
        encoded = base64.b64encode(Path(image_path).read_bytes()).decode("utf-8")
        body["input"]["image"] = {"data": encoded, "mime_type": "image/png"}
    headers = {**HEADERS, "Idempotency-Key": str(uuid.uuid4())}
    status, task = call(API, body, headers, "POST")
    print("created", task["id"], "HTTP", status)
    return task


def wait(task_id, interval=3, timeout=300):
    deadline = time.time() + timeout
    while time.time() < deadline:
        _, task = call(f"{API}/{task_id}")
        print(task["status"], task["progress"]["percent"])
        if task["status"] in ("succeeded", "failed", "canceled"):
            return task
        time.sleep(interval)
    # A timeout is not a failure: keep the task id and poll it again.
    raise TimeoutError(f"task {task_id} is still running")


def download(task, out_path):
    asset = task["assets"][0]
    with urllib.request.urlopen(asset["url"], timeout=120) as resp:
        Path(out_path).write_bytes(resp.read())
    print("saved", out_path, asset["mime_type"], asset["byte_size"], "bytes")


if __name__ == "__main__":
    task = create_task(
        "A vintage travel poster for the Night Train from Zürich to Milano, "
        "departing 21:40 from platform 7. A steam train crosses a stone arch "
        "bridge below snowy mountains under a deep blue night sky, with a cream "
        "border. Spell every word exactly as written.",
        aspect_ratio="3:4",
        image_size="2K",
    )
    task = wait(task["id"])
    if task["status"] != "succeeded":
        raise SystemExit(f"{task['status']}: {task.get('error')}")
    download(task, "night-train.jpg")

image_size에는 1K, 2K, 4K만 넣을 수 있습니다. Google 이미지 가이드는 "K는 반드시 대문자를 써야 하며, 소문자 파라미터(예: 1k)는 거부된다"고 분명히 적습니다. 2k가 아니라 2K를 보내십시오.

Idempotency-Key는 필수입니다. 요청마다 새 UUID를 만드는 것이 가장 간단합니다. 키를 다시 쓰는 경우는 똑같은 본문을 재시도할 때뿐입니다. OmniaKey는 원래 작업을 HTTP 200으로 돌려주므로 두 번 결제되지 않고, 같은 키에 본문이 다르면 409를 돌려줍니다.

생성 요청은 202와 id를 반환합니다. OmniaKey의 미디어 작업 문서에 전체 흐름이 있습니다. 끝난 작업은 다음 필드를 담고 있습니다.

json
{
  "id": "task_AbMRVqzSnVh0VwBj0S7NuMUxy5sZtfHP",
  "status": "succeeded",
  "progress": {"percent": 100, "stage": "completed"},
  "usage": {"total_tokens": 1133},
  "assets": [
    {
      "kind": "image",
      "mime_type": "…",
      "url": "https://…signed-url…"
    }
  ]
}

assets[0].url을 읽었으면 바로 내려받으십시오. 서명 링크는 한 시간쯤이면 만료되고 작업 기록은 24시간 남습니다. 형식은 mime_type 필드로 알 수 있고, Gemini 모델은 보통 JPEG를 돌려줍니다.

같은 스크립트를 1:1, 2K로 바꾸고 중국어 간판을 요청하면 이렇게 나옵니다.

벽돌 벽에 걸린 어두운 금속 간판. 중국어 세 줄과 전화번호가 적혀 있다
2K, 1:1: 2048x2048, 47.2초. 중국어 세 줄과 010-6789 4321이 모두 맞았고 첫 번째 0의 일부는 녹이 덮었습니다.

있는 이미지 편집하기

operation: "edit"을 쓰고 원본 이미지를 input.image에 {data, mime_type} 형태로 넣습니다. 위 스크립트는 image_path를 넘기면 이 경로를 탑니다.

python
task = create_task(
    "Hang this poster in a thin black frame on a warm white wall, with a brass "
    "picture light above it, a low wooden cabinet below and a trailing plant on "
    "the right. Keep the poster exactly as it is.",
    aspect_ratio="1:1",
    image_size="2K",
    image_path="night-train-2k.png",
)

실제로 나가는 본문은 다음과 같습니다.

json
{
  "model": "gemini-3-pro-image",
  "type": "image",
  "operation": "edit",
  "input": {
    "prompt": "Hang this poster in a thin black frame on a warm white wall…",
    "image": {"data": "…base64…", "mime_type": "image/png"}
  },
  "parameters": {"aspect_ratio": "1:1", "image_size": "2K"}
}

예제 원본이 PNG라서 스크립트가 image/png를 붙입니다. JPEG라면 이 값을 바꾸십시오. 참조 이미지를 여러 장 쓸 때는 input.images에 같은 {data, mime_type} 형태로 넣습니다. Google 가이드는 참조 이미지를 최대 14장으로 정합니다. gemini-3-pro-image의 배분은 사물 6장, 인물 5장, 스타일 참조 3장입니다.

본문은 1 MiB 아래로 유지하십시오. Base64로 바꾸면 파일이 삼분의 일쯤 커지므로 인코딩 전에 원본을 줄이거나 압축하십시오. 이번 편집에는 2K 포스터를 썼고 글자와 배치가 그대로 남았습니다.

벽에 걸린 검은 액자 속 야간 열차 포스터. 위에는 사진 조명, 오른쪽에는 화분
2K, 1:1 편집: 2048x2048, 49.9초. 포스터 글자와 디자인이 그대로 남았고 액자는 반듯한 직사각형입니다.

1K, 2K, 4K로 얻는 것

같은 3:4 포스터 프롬프트를 세 크기에서 한 번씩 돌렸습니다. 픽셀 수는 Google의 3:4 표와 정확히 맞았습니다. 1K는 896x1200, 2K는 1792x2400, 4K는 3584x4800입니다. 포스터 글자는 세 크기 모두 맞았습니다.

1K 야간 열차 포스터. 돌다리 위 증기 기관차, 눈 덮인 산, 짙은 파란 밤하늘
1K, 3:4: 896x1200, 28.4초. 포스터 네 줄이 모두 맞고 기관차의 램프와 연결봉이 서로 붙어 있습니다.
같은 야간 열차 포스터의 2K 버전. 오른쪽 비탈에 마을과 첨탑 두 개가 더 있다
2K, 3:4: 1792x2400, 48.2초. 글자는 같고 오른쪽 비탈에 마을과 첨탑 두 개가 생겼습니다.
같은 야간 열차 포스터의 4K 버전. 기관차 뒤에 객차 세 량과 석탄차가 그려져 있다
4K, 3:4: 3584x4800, 66.9초. 객차 세 량과 석탄차까지 그려졌고 벽돌 줄눈 두께는 고르지 않습니다.

크기가 커지면 늘어나는 것은 세부 묘사이고 철자는 그대로입니다.

  • 1K: 기관차 앞부분이 빽빽합니다. 램프, 바퀴, 연결봉이 서로 가깝게 붙습니다.
  • 2K: 오른쪽 비탈에 마을과 첨탑 두 개가 나타나고, 굴뚝에서는 옅은 연기가 올라가며, 돌다리에 아치가 일곱 개 보입니다.
  • 4K: 객차 세 량과 석탄차가 따로 그려지고 석조의 줄눈 두께도 달라집니다. 기관차 아래 기계는 여전히 단순합니다.

속도는 크기를 따라 늘었습니다. 28.4초, 48.2초, 66.9초입니다. 크기마다 한 번씩만 돌렸으므로 이 초는 한 번의 결과로 읽으십시오.

Google은 이미지 출력을 100만 토큰당 $120으로 계산하고, 표에는 1K와 2K가 1,120 토큰, 4K가 2,000 토큰으로 나옵니다. Google 자신의 추정치는 이렇게 나옵니다.

text
1,120 × $120 ÷ 1,000,000 = $0.134 per 1K image
1,120 × $120 ÷ 1,000,000 = $0.134 per 2K image
2,000 × $120 ÷ 1,000,000 = $0.24  per 4K image

우리 작업은 Google의 예시보다 토큰을 많이 보고했습니다. 같은 계산을 실측값으로 하면 다음과 같습니다.

text
1,133 × $120 ÷ 1,000,000 = $0.136 per 1K image
1,225 × $120 ÷ 1,000,000 = $0.147 per 2K image
2,480 × $120 ÷ 1,000,000 = $0.298 per 4K image
크기픽셀(3:4)실측 시간실측 토큰Google 추정
1K896x120028.4초1,133$0.134
2K1792x240048.2초1,225$0.134
4K3584x480066.9초2,480$0.24

OmniaKey는 이미지 호출을 정액으로 청구하고 토큰으로 계산하지 않으므로 위 토큰 수에 따라 요금이 달라지지 않습니다. 현재 호출당 요금은 모델 페이지에 있고 실패한 작업은 환불됩니다. 같은 세 장을 Google의 토큰 단가로 추정하면 $0.134, $0.134, $0.24입니다.

자주 만나는 오류

여기서 다루는 실패는 대부분 필드 이름이나 파일 크기 문제이고 모델 자체의 문제가 아닙니다. OmniaKey 미디어 문서에 적힌 것들입니다.

HTTP코드 또는 상태뜻할 일
400invalid_request본문 형식 오류, 지원하지 않는 작업, 잘못된 파라미터JSON과 아래 필드 이름을 확인하십시오
503no_available_channel지금 이 모델을 처리하는 채널이 없음모델을 바꾸십시오. 재시도로는 해결되지 않습니다
—failed작업이 끝났지만 결과가 없음error.code와 error.message를 읽으십시오

failed는 HTTP 오류가 아닙니다. 생성 요청은 이미 성공했으므로 작업을 폴링해서 error 객체를 읽어야 합니다. 실패와 취소 작업은 자동으로 환불됩니다.

원본을 그대로 Base64로 바꾸면 본문이 1 MiB를 넘기 쉽습니다. 먼저 줄이고 파일 크기가 아니라 인코딩 후 길이를 확인하십시오.

Google 가이드는 소문자 1k를 거부합니다. 1K, 2K, 4K 중 하나를 쓰십시오.

OmniaKey 이미지 문서도 Gemini 모델에서 size와 최상위 mime_type을 거부합니다. 둘 다 Gemini 파라미터가 아니기 때문입니다. 대신 parameters.aspect_ratio와 parameters.image_size를 쓰고 출력 형식은 응답의 mime_type 필드에서 읽으십시오.

자주 묻는 질문

Nano Banana Pro API의 모델 ID는 무엇인가요?

gemini-3-pro-image입니다. Google의 Vertex 모델 카드가 GA와 출시일 2026년 5월 28일을 적고 있고 OmniaKey도 같은 ID를 받습니다. 예전 ID인 gemini-3-pro-image-preview도 OmniaKey에서 되며 같은 모델을 가리킵니다.

Nano Banana Pro 이미지 한 장은 얼마인가요?

Google 추정치는 1K/2K가 $0.134, 4K가 $0.24입니다. 이미지 토큰 100만 개당 $120으로 계산한 값입니다. 우리 작업은 1,133, 1,225, 2,480 토큰이었고 같은 단가로는 $0.136, $0.147, $0.298입니다. OmniaKey는 모델 페이지에 호출당 요금을 따로 적습니다.

Nano Banana Pro가 중국어 글자도 그리나요?

그립니다. 2K 간판은 중국어 세 줄이 정확히 돌아왔습니다. 同福客栈, 住宿 餐饮 茶水, 010-6789 4321이고 숫자 11개와 하이픈도 맞았습니다.

1K, 2K, 4K 중 무엇을 고를까요?

이번 같은 포스터에는 2K가 쓰기 좋은 중간입니다. 세 크기 모두 글자를 맞혔으므로 4K에서 늘어나는 것은 세부(나뉜 객차, 벽돌 줄눈)이고 글자 정확도가 아닙니다. 896x1200이면 충분한 초안에는 1K가 빠릅니다.

편집에서 400 invalid_request가 나오면?

본문이 1 MiB를 넘었는지, 원본이 맞는 필드에 들어갔는지, Gemini 모델에 size나 최상위 mime_type을 보냈는지 확인하십시오. 이미지는 input.image에 {data, mime_type} 형태로 넣고 인코딩 후 본문을 1 MiB 아래로 유지하며 파라미터는 aspect_ratio와 image_size만 쓰십시오.

확인한 출처

업데이트 기록

  • 2026-11-03: 첫 버전. 같은 날 OmniaKey로 테스트 다섯 번 실행.

이 글에서 다루는 모델

Nano Banana ProGemini 이미지이미지 APIPythonAPI 튜토리얼

관련 글

최근 글

인기 가이드