ブログに戻る

Nano Banana Pro API の呼び出し方:完全な手順

E2X Team··読了時間 15分
nano-banana-proapi-tutorialimage-generationwebhooksgoogle

これは E2X API 上の Nano Banana Pro について、統合の最初から最後までの全体です。キーを取得し、job を submit し、response を読み、正しく待ち、失敗しうる経路を扱い、そしてバイト列を明日もそこにある場所へ置く。

人がつまずくのは最後の段階です。すでに画像を生成していて、自分のものがなぜ消えたのかだけを知りたい方は、そこまで飛ばしてください。

チャコールの背景に冷たいシアンの縁光を受けた、暗いアルマイト仕上げのスチール製の機械工用精密機器の筐体。文字のない無地の目盛り盤と、ローレット加工の調整ノブが見える

Pro があなたの仕事に正しいティアかどうかは別の問いで、それは価格と速度の記事で別途お答えしました。短く言えば、1 request $0.075、およそ 30 秒、画像に読める文字が入るか、異なる役割を担う参照画像を組み合わせるのであれば見合います。このページは、あなたがすでに判断済みであることを前提とします。

最初の request の前に必要なもの

4 つあり、うち 3 つはそれぞれ 1 行です。

  • 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 body へそのまま入り、あいまい一致はありません。
  • 送るに値する prompt。 Pro は flash ティアより具体性に報います。実行する余地が大きいからです。「猫、ただし映画的に」より深く踏み込みたい方にはprompt ガイドを別に用意しています。

契約は非同期です。job を submit すると 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 を死んだ値として扱ってください。最初の 2 つの設定はひとつの定額に乗っており — 2026年8月26日 に確認した、どちらも $0.075 — 小さいほうは同じ請求行に対してピクセル数が少ないだけです。数字を動かすのは 4k だけで、$0.15、上限は 4096×4096 です。

マットなチャコールの機器筐体に埋め込まれた、ローレット加工のスチール製調整ノブ 1 個の極端なマクロ。機械加工の溝に沿って冷たいハイライトが 1 本走っている

返ってくるもの

submit の呼び出しは job のエンベロープを即座に返します。必要なフィールドは data.jobId です。

{
  "success": true,
  "data": {
    "jobId": "job_8Kd2mQvXpL",
    "status": "pending"
  }
}

ステータスは pending → processing → completed と進むか、2 つある終端の失敗、failed と cancelled のいずれかに着地します。完了した job は結果を data.outputs[0].url に、失敗した job は理由を data.error.message に持ちます。

課金のコードを書く前に体に入れておくべき細部がひとつ。API が返す金額の値はすべてマイクロセント単位です。 100 万が 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 秒のスリープのループで包めば、動く統合になります。とくに Nano Banana Pro については、固定間隔でも本当に許容できます。request はおよそ 30 秒走るので、ステータスの呼び出しはおおよそ 15 回で終わります。誰かの迷惑になる量の通信ではありません。

それでも 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")

このループが、素朴な実装がたいていしないことを 2 つしています。固定の締め切りを持つので、詰まった job はプロセスが殺されるまで回り続けるのではなく例外を上げます。そして通信層では 3 つの終端状態を同じように扱い、ペイロードを返して、失敗が何を意味するかは呼び出し側に決めさせます。再試行のロジックはこの関数の内側ではなく、上に属します。

開いた精密機器の内部、磨かれたスチールの脱進機の歯車とアンクル。極端に浅い被写界深度、チャコールの上に冷たい青灰色の光

webhook と、そちらへ移るべき理由

submit の body に 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 秒で試せるからです。定常状態としては誤りで、理由は算数です。1 枚ずつなら polling の代償はループ 1 本。batch で 200 枚なら代償は 200 本の同時ループで、それぞれが 30 秒接続を保持し、しかもそのプロセスは、処理中のすべての job を見失うことなしには再起動できなくなっています。

webhook は仕事を再開可能にします。submit のときに job ID がデータベースへ入り、当社が呼び出したときにハンドラーがその行を更新し、途中でデプロイが入っても何も変わりません。スクリプトではなく生成のパイプラインを組んでおられるなら、作るべきはこちらの版であり、画像生成を端から端まで自動化するの型と組み合わさります。

運用上の注記を 2 つ。endpoint は公開インターネットから到達可能でなければならないため、localhost の URL は開発中に黙って一度も発火しません。トンネルをお使いください。そして webhook は真実の源ではなく通知として扱ってください。ハンドラーの内側で、行動を起こす前に job を ID で取得しましょう。

生成ではなく、画像を編集する

エンベロープは同じ、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 は参照画像を最大 14 枚受け取り、他のティアと違ってそれを役割で分けます。キャラクターの同一性に最大 5 枚、物体の忠実性に最大 6 枚、スタイルに最大 3 枚。この役割分離こそ、そもそもこのティアにいる理由です。役割ごとの参照に使う正確なフィールド名はモデルごとに機械可読の仕様ファイルに記載されており、当社のドキュメントとこの記事が食い違うことがあれば、そちらが正です。キャラクターの一貫性のワークフローについては、一貫した生成のガイドで深く掘っています。

すべてのパラメーターと、その既定値

フィールド置き場所受け付ける値既定
modelbody の直下google/nano-banana-pro/text-to-image または google/nano-banana-pro/edit-image必須
input.promptinput の中文字列必須
input.aspect_ratioinput の中9:16、16:9、1:1、2:3、3:2、21:9、3:4、4:3、4:5、5:49:16
input.resolutioninput の中1k、2k、4k明示的に固定を
input.image_urlsinput の中、edit の slug のみ公開されて到達可能な URL の配列edit では必須
webhookUrlbody の直下公開された HTTPS の endpointなし、代わりに polling

出費につながる行は aspect_ratio と resolution の 2 つです。それ以外は想像どおりに振る舞います。

job が完了しないとき

失敗の面は 3 つあり、それぞれ扱いが違います。

submit の呼び出し自体が失敗する。 これは job が存在する前の HTTP エラーです。誤ったキー、崩れた body、未知のモデル slug。キューには何も入っておらず、課金も発生していません。request を直してください。変えずに再試行すれば同じように失敗します。

job が failed に至る。 job は存在し、モデルが画像を作らなかった場合です。理由は data.error.message を読んでください。たいていはコンテンツポリシーによる拒否か、image_urls に到達できない URL があるといった不正な入力です。ポリシーによる拒否をやみくもに再試行すれば同じように失敗しますし、上流の一時的なエラーの後の再試行はたいてい成功します。ステータスだけでなく、メッセージをログへ残してください。

job が cancelled に至る。 終端であり、バグではありません。コードの水準では failed とまったく同じに扱ってください。その行は閉じており、出力は来ません。

待機のループが timeout する。 これは job の状態ではありません。job はまだ走っていて、あなたの忍耐が尽きたということです。job ID はまだ有効ですので、再 submit するのではなく記録して後で確認してください。再 submit は同じ画像に 2 回課金することになります。

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"]

出力は失効する前にダウンロードする

公開の 1 週間後に壊れた画像を世に出すのはこの部分なので、節をひとつ割きます。

data.outputs[0].url の URL は一時的です。 これは配信用の URL であって、ホスティングではありません。その文字列を image_url という名前のデータベースの列に書き込んで商品ページで表示すれば、ステージングでは動き、レビューでも動き、公開日にも動き、そしてオブジェクトが期限を迎えたところで静かに画像の壊れたアイコンに変わります。

対処は 1 段階で、任意ではありません。バイト列を取得し、自分のストレージへ置き、自分の 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

ダウンロードは、完了を扱ったのと同じ作業単位の内側で行ってください。1 時間後の cron ではなく、初回の表示時に遅延評価するのでもなく。この猶予は、テストでは遅らせても逃げ切れる程度には長く、負荷の下では逃げ切れない程度には短い。

濃いグレーのフォームの切り抜きが敷かれた、機械加工されたアルミの標本ケースが開いている。ひとつには磨かれたスチールの円板が収まり、隣の切り抜きは空のまま

全体を、ひとつのスクリプトに

ここまでのすべてを組み立てたものです。E2X_API_KEY を設定して実行してください。

#!/usr/bin/env python3
"""Nano Banana Pro で画像を 1 枚生成し、ローカルに保存する。"""
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 のフィールドだけで、どれに向けるべきかはモデル比較の記事が扱っています。

コードではない最後の 1 点。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 の body に 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 秒かかるため、200 枚の batch は 200 本の同時 polling ループを意味し、それらは次のデプロイで揃って状態を失います。webhookUrl を使えば、submit の時点で job ID がデータベースに入り、ハンドラーが後から引き取ります。

Nano Banana Pro で既存の画像を編集するにはどうしますか?

slug に google/nano-banana-pro/edit-image を使い、input オブジェクトに prompt と並べて image_urls の配列を足してください。URL は公開されて取得可能でなければならないので、ストレージからの署名付き URL は機能しますが、ローカルのパスは機能しません。編集の費用は生成と同じ $0.075 です。

Nano Banana Pro にはどの解像度を要求すべきですか?

2k を送ってください。この slug では最初の 2 つの設定がひとつの定額を共有するため、1k は厳密に劣ります。ピクセルは少なく、請求行は同じです。4k へ手を伸ばすのは 4096×4096 の上限が本当に必要なときだけにしてください。課金が 2 倍の $0.15 になります。3 つの値はいずれも小文字です。

E2X API が 75000 のような価格を返すのはなぜですか?

API の金額の値はすべてマイクロセントで表され、1,000,000 が 1 米ドルです。Nano Banana Pro の job に載った 75000 は $0.075 です。保存には整数のまま保ち、人が読む地点でだけ割ってください。そうすれば請求期間を通じて丸め誤差が積み上がりません。