Nano Banana Pro API 호출하기: 처음부터 끝까지
E2X API에서 Nano Banana Pro를 쓰는 통합 전체를, 처음부터 끝까지 다룹니다. 키를 발급받고, job을 제출하고, response를 읽고, 제대로 기다리고, 실패할 수 있는 방식들을 처리하고, 바이트를 내일도 거기 있을 곳에 두는 일입니다.
사람들이 걸리는 것은 그 마지막 단계입니다. 이미 이미지를 생성하고 계시고 왜 여러분 이미지가 사라졌는지만 알고 싶으시다면 거기로 건너뛰십시오.

Pro가 여러분 작업에 맞는 티어인지는 다른 질문이고 가격과 속도에 관한 글에서 따로 답했습니다. 짧은 버전은 이렇습니다. request당 $0.075, 30초쯤, 그리고 여러분의 이미지가 읽히는 텍스트를 싣거나 서로 다른 역할을 지는 참조들을 결합한다면 값을 합니다. 이 페이지는 이미 결정하셨다고 전제합니다.
첫 request 전에 필요한 것
넷이고, 그중 셋은 한 줄씩입니다.
- API 키. E2X 계정에서 만드십시오. 아래의 모든 것이 그것을
Authorization: Bearer $E2X_API_KEY로 보냅니다. 서버 쪽에 두십시오. 브라우저 JavaScript 안의 키는 이제 다른 사람이 쓰고 있는 키입니다. - 베이스 URL. 카탈로그의 모든 모델에 대해
https://api.e2x.ai/v1입니다. - 올바른 slug. 생성은
google/nano-banana-pro/text-to-image입니다. 편집은google/nano-banana-pro/edit-image입니다. 그 문자열들은 request 본문에 그대로 들어가고, 유사 매칭 같은 것은 없습니다. - 보낼 만한 prompt. Pro는 flash 티어보다 구체성에 더 보상합니다. 그것을 실행에 옮길 여지가 더 크기 때문입니다. "고양이인데 시네마틱하게"보다 깊이 들어가고 싶으시면 별도의 prompt 작성 가이드가 있습니다.
계약은 비동기입니다. job을 제출하시면 ID를 받으시고, 이미지는 나중에 도착합니다. 픽셀이 준비될 때까지 블로킹하는 동기 endpoint는 없으며, Pro가 30초쯤 걸린다는 점을 감안하면 그런 것을 원하지도 않으실 겁니다.
첫 text-to-image request
curl -X POST https://api.e2x.ai/v1/jobs/submit \
-H "Authorization: Bearer $E2X_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "google/nano-banana-pro/text-to-image",
"input": {
"prompt": "대리석 커피 카운터 뒤에 걸린 손글씨 칠판 메뉴판, 왼쪽 창에서 들어오는 따뜻한 오후 빛, 얕은 피사계 심도",
"aspect_ratio": "16:9",
"resolution": "2k"
}
}'
같은 것을 Python으로 쓰면 이렇습니다.
import os
import requests
BASE = "https://api.e2x.ai/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['E2X_API_KEY']}"}
def submit(prompt, aspect_ratio="16:9", resolution="2k"):
r = requests.post(
f"{BASE}/jobs/submit",
headers=HEADERS,
json={
"model": "google/nano-banana-pro/text-to-image",
"input": {
"prompt": prompt,
"aspect_ratio": aspect_ratio,
"resolution": resolution,
},
},
timeout=30,
)
r.raise_for_status()
return r.json()["data"]["jobId"]
두 예제 모두 의도적으로 aspect_ratio와 resolution을 고정합니다. 그것들을 빼시기 전에 두 기본값을 이해하실 만합니다.
aspect_ratio의 기본값은 9:16입니다. 1:1이 아닙니다. 빼시면 생성하시는 모든 이미지가 영원히 세로가 되고, 레이아웃의 가로로 넓은 칸이 이상해 보일 때까지 알아차리지 못하십니다. 저희 API의 다른 어떤 필드보다 많은 통합이 여기에 걸립니다. 고정하십시오.
resolution 값은 소문자입니다. 1k, 2k, 4k입니다. 대문자 2K는 같은 문자열이 아니고 API가 그렇게 알려 드립니다.
이 slug에서 1k는 죽은 값으로 다루십시오. 앞의 두 설정은 하나의 평평한 과금에 앉아 있어서 — 2026년 8월 26일 확인 기준으로 어느 쪽이든 $0.075 — 작은 쪽은 같은 청구 줄에 더 적은 픽셀을 돌려줄 뿐입니다. 숫자를 움직이는 것은 4k뿐입니다. $0.15이고 천장은 4096×4096입니다.

무엇이 돌아오는가
submit 호출은 job 봉투와 함께 즉시 반환됩니다. 필요하신 필드는 data.jobId입니다.
{
"success": true,
"data": {
"jobId": "job_8Kd2mQvXpL",
"status": "pending"
}
}
상태는 pending → processing → completed로 흐르거나, 두 종단 실패 중 하나인 failed나 cancelled에 안착합니다. 완료된 job은 결과를 data.outputs[0].url에, 실패한 job은 사유를 data.error.message에 싣습니다.
과금 코드를 쓰시기 전에 몸에 익히실 세부 하나. API가 돌려주는 모든 금액 값은 마이크로센트입니다. 백만이 1달러입니다. 따라서 Nano Banana Pro request는 0.075가 아니라 75000으로 돌아옵니다. 1,000,000으로는 표시 계층에서만 나누시고 다른 어디에서도 나누지 마십시오. 그리고 나눈 숫자는 절대 저장하지 마십시오.
두들기지 않고 polling하기
투박한 버전도 작동합니다.
curl https://api.e2x.ai/v1/jobs/job_8Kd2mQvXpL \
-H "Authorization: Bearer $E2X_API_KEY"
저것을 고정 2초 sleep이 든 루프로 감싸시면 굴러가는 통합이 됩니다. 특히 Nano Banana Pro라면 고정 간격도 진짜로 받아들일 만합니다. request가 30초쯤 도니 상태 호출을 열다섯 번쯤 하고 멈춥니다. 누구를 성가시게 할 만한 트래픽이 아닙니다.
그래도 backoff가 낫고, 이유는 예의와 아무 상관이 없습니다. 고정 간격은 편차를 숨깁니다. job이 30초가 아니라 90초 걸리면, 고정 루프는 같은 속도로 계속 호출하고 여러분의 로그는 정상 실행과 똑같아 보입니다. 늘어나는 backoff는 느린 job을 눈에 띄게 느리게 만들어 줍니다.
import time
TERMINAL = {"completed", "failed", "cancelled"}
def wait_for(job_id, timeout=300):
delay = 2.0
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
r = requests.get(f"{BASE}/jobs/{job_id}", headers=HEADERS, timeout=30)
r.raise_for_status()
data = r.json()["data"]
if data["status"] in TERMINAL:
return data
time.sleep(delay)
delay = min(delay * 1.4, 10.0)
raise TimeoutError(f"{job_id} still running after {timeout}s")
저 루프가 순진한 루프는 보통 하지 않는 일 둘을 합니다. 하드 데드라인이 있어서, 갇힌 job이 프로세스가 죽을 때까지 도는 대신 예외를 냅니다. 그리고 전송 계층에서 세 종단 상태를 똑같이 다뤄, 페이로드를 돌려주고 실패가 무엇을 뜻하는지는 호출자가 정하게 둡니다. 재시도 로직은 이 함수 안이 아니라 위에 속합니다.

Webhook, 그리고 그쪽으로 옮겨야 하는 이유
submit 본문에 webhookUrl을 넘기시면 job이 종단 상태에 닿을 때 저희가 불러 드립니다. polling 루프가 아예 없습니다.
curl -X POST https://api.e2x.ai/v1/jobs/submit \
-H "Authorization: Bearer $E2X_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "google/nano-banana-pro/text-to-image",
"input": {
"prompt": "제도용 책상 위에 놓인 인도교 건축 모형, 북측 채광",
"aspect_ratio": "3:2",
"resolution": "2k"
},
"webhookUrl": "https://your-app.example.com/hooks/e2x"
}'
polling은 첫 수로는 옳습니다. 터미널에서 10초면 테스트할 수 있으니까요. 정상 상태로는 틀렸고, 이유는 산수입니다. 한 번에 이미지 하나라면 polling의 값은 루프 하나입니다. batch로 이미지 이백 장이면 polling의 값은 동시 루프 이백 개이고, 각각이 30초 동안 연결을 붙들며, 그 프로세스는 이제 진행 중인 모든 job을 놓치지 않고는 재시작할 수 없습니다.
webhook은 그 일을 재시작 가능하게 만듭니다. 제출할 때 job ID가 여러분 데이터베이스로 들어가고, 저희가 호출하면 핸들러가 그 행을 갱신하며, 중간에 배포가 있어도 아무것도 달라지지 않습니다. 스크립트가 아니라 생성 파이프라인을 만들고 계시다면 이쪽이 만드실 버전이고, 이미지 생성을 끝에서 끝까지 자동화하기의 패턴들과 짝을 이룹니다.
운영상의 참고 둘입니다. 여러분 endpoint는 공용 인터넷에서 닿을 수 있어야 하므로, 개발 중에는 localhost URL이 소리 없이 영영 발화하지 않습니다. 터널을 쓰십시오. 그리고 webhook을 진실의 출처가 아니라 알림으로 다루십시오. 핸들러 안에서 그것에 따라 행동하시기 전에 ID로 job을 조회하십시오.
생성 대신 이미지 편집하기
같은 봉투, 다른 slug, 추가 필드 하나입니다. edit endpoint는 prompt와 함께 image_urls를 받고 값은 똑같이 $0.075입니다.
curl -X POST https://api.e2x.ai/v1/jobs/submit \
-H "Authorization: Bearer $E2X_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "google/nano-banana-pro/edit-image",
"input": {
"prompt": "배경을 부드러운 회색 스튜디오 스위프로 교체하고, 제품 조명은 지금 그대로 유지하세요",
"image_urls": ["https://your-cdn.example.com/source/bottle.jpg"],
"aspect_ratio": "1:1",
"resolution": "2k"
}
}'
넘기시는 URL은 공개적으로 가져올 수 있어야 합니다. 여러분 저장소에서 발급한 서명 URL은 통하고, 여러분 노트북의 경로는 통하지 않습니다.
Nano Banana Pro는 참조 이미지를 최대 열네 장 받고, 다른 티어들과 달리 그것들을 역할로 나눕니다. 캐릭터 정체성에 최대 5장, 오브젝트 충실도에 최대 6장, 스타일에 최대 3장입니다. 애초에 이 티어에 있을 이유가 그 역할 분할이며, 역할이 지정된 참조의 정확한 필드 이름은 모델별로 기계가 읽는 스펙 파일에 문서화되어 있습니다. 저희 문서와 이 글이 어긋나는 일이 생긴다면 그쪽이 권위입니다. 캐릭터 일관성 워크플로만 놓고는 일관된 생성 가이드에서 더 깊이 들어갑니다.
모든 파라미터와 기본값
| 필드 | 들어가는 위치 | 허용 값 | 기본값 |
|---|---|---|---|
model | 본문 최상위 | google/nano-banana-pro/text-to-image 또는 google/nano-banana-pro/edit-image | 필수 |
input.prompt | input 안 | 문자열 | 필수 |
input.aspect_ratio | input 안 | 9:16, 16:9, 1:1, 2:3, 3:2, 21:9, 3:4, 4:3, 4:5, 5:4 | 9:16 |
input.resolution | input 안 | 1k, 2k, 4k | 명시적으로 고정하십시오 |
input.image_urls | input 안, edit slug 전용 | 공개적으로 닿을 수 있는 URL 배열 | edit에서 필수 |
webhookUrl | 본문 최상위 | 공개 HTTPS endpoint | 없음, 대신 polling |
사람들 돈을 쓰게 만드는 두 줄은 aspect_ratio와 resolution입니다. 나머지는 전부 짐작하시는 대로 굽니다.
job이 완료되지 않을 때
실패 표면이 셋이고, 각각 다른 처리가 필요합니다.
submit 호출 자체가 실패합니다. job이 존재하기도 전의 HTTP 에러입니다. 잘못된 키, 형식이 깨진 본문, 모르는 모델 slug 같은 것들이요. 아무것도 큐에 들어가지 않았고 아무것도 청구되지 않았습니다. request를 고치십시오. 그대로 재시도하면 똑같이 실패합니다.
job이 failed에 닿습니다. job은 존재했고 모델이 이미지를 내놓지 못했습니다. 사유는 data.error.message를 읽으십시오. 보통 콘텐츠 정책 거부이거나 image_urls에 닿을 수 없는 URL이 들어간 것 같은 잘못된 입력입니다. 정책 거부를 맹목적으로 재시도하면 똑같이 실패하고, 일시적인 상위 오류 뒤의 재시도는 대개 성공합니다. 상태만 로그로 남기지 마시고 메시지를 로그로 남기십시오.
job이 cancelled에 닿습니다. 종단이고 버그가 아닙니다. 코드 수준에서는 failed와 정확히 똑같이 다루십시오. 그 행은 닫혔고 출력은 오지 않습니다.
대기 루프가 timeout 됩니다. job의 상태가 아닙니다. job은 아직 돌고 있고 여러분의 인내가 바닥났다는 뜻입니다. job ID는 여전히 유효하므로, 재제출하는 대신 기록해 두고 나중에 다시 확인하십시오. 재제출은 같은 이미지에 값을 두 번 물립니다.
def generate(prompt, **kw):
job_id = submit(prompt, **kw)
job = wait_for(job_id)
if job["status"] != "completed":
reason = (job.get("error") or {}).get("message", "no reason given")
raise RuntimeError(f"{job_id} ended as {job['status']}: {reason}")
return job["outputs"][0]["url"]
만료되기 전에 출력을 내려받으십시오
출시 일주일 뒤에 깨진 이미지를 배포하게 만드는 부분이라 절을 따로 두었습니다.
data.outputs[0].url의 URL은 임시입니다. 호스팅이 아니라 전달용 URL입니다. 그 문자열을 image_url이라는 데이터베이스 컬럼에 쓰시고 제품 페이지에 렌더링하시면, 페이지는 스테이징에서 되고, 검수에서 되고, 출시일에도 되다가, 오브젝트가 만료되는 순간 조용히 깨진 이미지 아이콘으로 변합니다.
해법은 한 단계이고 선택이 아닙니다. 바이트를 가져오시고, 여러분의 저장소에 넣으시고, 여러분의 URL을 저장하십시오.
import pathlib
def download(url, dest):
with requests.get(url, stream=True, timeout=120) as r:
r.raise_for_status()
pathlib.Path(dest).parent.mkdir(parents=True, exist_ok=True)
with open(dest, "wb") as f:
for chunk in r.iter_content(chunk_size=1 << 16):
f.write(chunk)
return dest
또는 셸에서 이렇게요.
curl -sL "$OUTPUT_URL" -o ./out/menu-board.jpg
다운로드는 완료를 처리한 것과 같은 작업 단위 안에서 하십시오. 한 시간 뒤의 cron에서도, 첫 페이지 조회 때 게으르게도 아닙니다. 그 창은 테스트에서는 지연이 있어도 넘어갈 만큼 넉넉하고, 부하가 걸리면 넘어갈 수 없을 만큼 좁습니다.

전체를 스크립트 하나로
위의 모든 것을 조립했습니다. E2X_API_KEY를 설정하시고 실행하십시오.
#!/usr/bin/env python3
"""Nano Banana Pro로 이미지 하나를 생성해 로컬에 저장합니다."""
import os
import pathlib
import time
import requests
BASE = "https://api.e2x.ai/v1"
MODEL = "google/nano-banana-pro/text-to-image"
HEADERS = {"Authorization": f"Bearer {os.environ['E2X_API_KEY']}"}
TERMINAL = {"completed", "failed", "cancelled"}
def submit(prompt, aspect_ratio="16:9", resolution="2k"):
r = requests.post(
f"{BASE}/jobs/submit",
headers=HEADERS,
json={
"model": MODEL,
"input": {
"prompt": prompt,
"aspect_ratio": aspect_ratio,
"resolution": resolution,
},
},
timeout=30,
)
r.raise_for_status()
return r.json()["data"]["jobId"]
def wait_for(job_id, timeout=300):
delay = 2.0
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
r = requests.get(f"{BASE}/jobs/{job_id}", headers=HEADERS, timeout=30)
r.raise_for_status()
data = r.json()["data"]
if data["status"] in TERMINAL:
return data
time.sleep(delay)
delay = min(delay * 1.4, 10.0)
raise TimeoutError(f"{job_id} still running after {timeout}s")
def download(url, dest):
with requests.get(url, stream=True, timeout=120) as r:
r.raise_for_status()
pathlib.Path(dest).parent.mkdir(parents=True, exist_ok=True)
with open(dest, "wb") as f:
for chunk in r.iter_content(chunk_size=1 << 16):
f.write(chunk)
return dest
def main():
job_id = submit(
"대리석 커피 카운터 뒤에 걸린 손글씨 칠판 메뉴판, "
"왼쪽 창에서 들어오는 따뜻한 오후 빛, 얕은 피사계 심도"
)
print("submitted", job_id)
job = wait_for(job_id)
if job["status"] != "completed":
reason = (job.get("error") or {}).get("message", "no reason given")
raise SystemExit(f"{job_id} ended as {job['status']}: {reason}")
path = download(job["outputs"][0]["url"], "out/menu-board.jpg")
print("saved", path)
if __name__ == "__main__":
main()
slug를 google/nano-banana-pro/edit-image로 바꾸고 image_urls를 더하시면 같은 스크립트가 생성 대신 편집을 합니다. Nano Banana 2나 Nano Banana 2 Lite로 바꾸셔도 그대로 돌아갑니다. 봉투가 text-to-image 카테고리의 모든 것과 image-to-image의 모든 것에 걸쳐 동일하기 때문입니다. 다른 것은 input 필드뿐이고, 어느 쪽에 겨눌지는 모델 비교 글이 다룹니다.
코드가 아닌 마지막 하나. 모든 Google 모델에서 나오는 모든 이미지는 SynthID watermark를 달고, 어떤 프로바이더도 그것을 끌 수 없습니다. 그 위에 무언가를 세우시기 전에 계약에 서명하는 사람과 그 문제를 정리하십시오.
자주 묻는 질문
Nano Banana Pro API를 어떻게 호출합니까?
https://api.e2x.ai/v1/jobs/submit으로 bearer token과 모델 slug google/nano-banana-pro/text-to-image, 그리고 prompt가 담긴 input 객체를 실어 POST를 보내십시오. response가 data.jobId를 돌려줍니다. https://api.e2x.ai/v1/jobs/{id}를 polling하시거나 submit 본문에 webhookUrl을 넘기신 다음, 완성된 이미지를 data.outputs[0].url에서 읽으십시오.
Nano Banana Pro의 기본 aspect ratio는 무엇입니까?
세로인 9:16입니다. 이건 거의 모두를 놀래킵니다. 이미지 API 대부분이 정사각형을 기본으로 삼기 때문입니다. aspect_ratio를 명시적으로 설정하지 않으시면 모든 이미지가 세로로 나옵니다. 모델은 9:16, 16:9, 1:1, 2:3, 3:2, 21:9, 3:4, 4:3, 4:5, 5:4를 받습니다.
Nano Banana Pro 이미지 URL이 깨지는 이유는 무엇입니까?
저희가 돌려드리는 출력 URL이 영구 호스팅이 아니라 임시 전달이기 때문입니다. 저희 URL을 데이터베이스에 저장했다가 나중에 렌더링하는 파이프라인은 오브젝트가 만료되는 순간 깨진 이미지를 보여 줍니다. job 완료를 처리하는 바로 그 단계에서 바이트를 내려받아 여러분의 버킷에 저장하십시오.
E2X API에는 polling을 써야 합니까, webhook을 써야 합니까?
만드시는 동안은 polling하십시오. 터미널에서 테스트할 수 있으니까요. 프로덕션에서 도는 것은 전부 webhook으로 옮기십시오. Nano Banana Pro job은 30초쯤 걸리므로, 이백 장짜리 batch는 다음 배포에서 상태를 전부 잃는 동시 polling 루프 이백 개를 뜻합니다. webhookUrl을 쓰시면 제출 시점에 job ID가 여러분 데이터베이스로 들어가고 핸들러가 나중에 집어 갑니다.
Nano Banana Pro로 기존 이미지를 어떻게 편집합니까?
slug를 google/nano-banana-pro/edit-image로 쓰시고 prompt와 함께 input 객체에 image_urls 배열을 더하십시오. URL은 공개적으로 가져올 수 있어야 하므로 여러분 저장소의 서명 URL은 통하고 로컬 경로는 통하지 않습니다. 편집은 생성과 똑같이 $0.075입니다.
Nano Banana Pro에는 어느 해상도를 요청해야 합니까?
2k를 보내십시오. 이 slug에서 앞의 두 설정이 하나의 평평한 과금을 공유하므로 1k는 완전히 열등합니다. 픽셀은 적고 청구 줄은 같습니다. 4k는 4096×4096 천장이 진짜로 필요하실 때만 손을 뻗으십시오. 과금이 $0.15로 두 배가 되니까요. 세 값 모두 소문자입니다.
E2X API가 75000 같은 가격을 돌려주는 이유는 무엇입니까?
API의 모든 금액 값이 마이크로센트로 표현되기 때문입니다. 1,000,000이 1미국달러입니다. Nano Banana Pro job의 75000은 $0.075입니다. 저장소에는 정수를 두시고 사람이 읽는 지점에서만 나누십시오. 그래야 반올림이 청구 주기 내내 누적되지 않습니다.