◉GEMINI LABEN
●ARGON — gemini-4-argon が 10/13 に GA。複雑なコーディング・エージェント・数学・科学向けです●DEEP RES — deep-research-pro-preview-12-2025 は 10/23 に停止、残り7日。移行先は deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 が GA(10/06)。gemini-3.1-flash-image は非推奨で停止日は未発表です●MCP — ツール結果が 8.5万トークンに膨らんだ事例を、ADK で 310 トークンに包んで抑えた報告が出ています●FLASH — gemini-3.7-flash と 3.5-flash は新しい Flash モデルに自動で回されます(10/08)●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…●ARGON — gemini-4-argon が 10/13 に GA。複雑なコーディング・エージェント・数学・科学向けです●DEEP RES — deep-research-pro-preview-12-2025 は 10/23 に停止、残り7日。移行先は deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 が GA(10/06)。gemini-3.1-flash-image は非推奨で停止日は未発表です●MCP — ツール結果が 8.5万トークンに膨らんだ事例を、ADK で 310 トークンに包んで抑えた報告が出ています●FLASH — gemini-3.7-flash と 3.5-flash は新しい Flash モデルに自動で回されます(10/08)●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…
記事一覧/API / SDK
◈ API / SDK/2026-10-16中級

models は 200 なのに generateContent だけ 403 になる日 — 切り分けの順番を先に決めておく

モデル一覧の取得は 200 で通るのに、生成リクエストだけが 403 permission_denied で止まるときの切り分け手順です。キー・プロジェクト・請求の順に確かめる小さな診断スクリプトと、リトライしてはいけない理由、サポートへ渡す材料の揃え方をまとめました。

gemini-api287troubleshooting59403permission-deniedbilling3python106

フォーラムで、ある開発者の投稿を読んでいました。請求は有効になっており、Generative Language API も有効で、キーも正しいと確認されていました。モデル一覧の取得は 200 で返ってきます。それなのに生成リクエストだけが 403、エラーコードは permission_denied で、本文は「Your project has been denied access. Please contact support.」。

投稿には、解決の報告がありませんでした。

読み終えてから、胃のあたりが少し重くなりました。自分のプロジェクトで同じことが起きたら、私はおそらく最初の一時間を、キーを作り直して、リトライを回して、設定画面を行き来することに使うだろうと思ったからです。個人開発で Gemini API を呼んでいる身として、症状が出てから考えるのでは遅いと感じ、切り分けの順番を先に書き出しておくことにしました。

この記事は、その順番の記録です。私自身がこの 403 に遭遇したわけではなく、公式ドキュメントとフォーラムの報告から組み立てた手順だという点は、先にお伝えしておきます。

最初にお伝えしたいのは「リトライしない」ことです

公式のトラブルシューティングには、403 のようなクライアントエラーはリトライしないよう書かれています。理由として挙がっているのは、無効な API キー、Prepay クレジットの枯渇、リクエストの構文エラーといった「待っても直らない種類の問題」だからです。リトライが向くのは 429 や 5xx のような一時的なエラーです。

エラー表でも、permission_denied の説明は「API キーにこのリソースへの権限がない」、対処は「キーの権限とプロジェクトへのアクセスを確認する」とされています。つまり、直す場所はコードの外にあります。

HTTPコード待てば直るか最初に見る場所
400failed_preconditionいいえ請求が無効になっていないか
402payment_requiredいいえPrepay 残高
403permission_deniedいいえキーの権限・プロジェクトの状態
429rate_limit_exceededはい待って指数バックオフ
503service_unavailableはい待って指数バックオフ

429 の側の見分け方は、別の記事に書いています。Gemini API の 429 を全部リトライしてはいけません — レート制限と Spend Cap 枯渇を見分けるリトライ設計も合わせてご覧ください。

「models が通る」ことは、生成が通る証拠になりません

モデル一覧の取得が 200 で返るのは、キーが認識され、プロジェクトが存在することまでしか示しません。生成リクエストは、そのプロジェクトに対する別の判定を通ります。ですから、一覧が返ったので鍵は問題ない、と結論するのは早すぎます。

私が先に決めた順番は、次の三段です。

  1. キー: そのキーは、想定のプロジェクトのものか。制限(HTTP リファラーなど)が付いていないか
  2. プロジェクト: 想定のプロジェクトは、AI Studio の Projects ページでどの状態に見えるか
  3. 請求: 請求アカウントに紐づいているか。Prepay なら残高があるか

キーの制限が原因なら、別の記事のGemini API キーに HTTP リファラー制限をかけたら本番サイトから 403 エラーになる原因と対処が当たります。こちらは設定を直せば解消する、救いのある 403 です。

厄介なのは、キーにも請求にも見える範囲で問題がないのに、プロジェクト単位で拒否されている場合です。フォーラムの投稿が、まさにこの形でした。

三段を機械的に確かめる小さなスクリプト

手で順番に叩くと、途中で気が変わります。そこで、モデル一覧と生成を同じキーで続けて叩き、結果を並べて出すだけのスクリプトを用意しました。複数のキーを渡せば、同じ表に並びます。

目的は、「どのキーで、どのエンドポイントが、どの status で返ったか」を一枚にすることです。サポートへ渡す材料にも、そのまま使えます。

import json
import os
import sys
import urllib.error
import urllib.request
 
BASE = "https://generativelanguage.googleapis.com/v1beta"
# 差し替えてください。確認したいモデル ID を環境変数で渡せます。
MODEL = os.environ.get("GEMINI_MODEL", "gemini-3.8-flash")
 
 
def call(method, path, key, body=None):
    """1 回だけ呼ぶ。リトライはしない(403 は待っても直らないため)。"""
    data = json.dumps(body).encode() if body is not None else None
    req = urllib.request.Request(
        f"{BASE}{path}",
        data=data,
        method=method,
        headers={"x-goog-api-key": key, "Content-Type": "application/json"},
    )
    try:
        with urllib.request.urlopen(req, timeout=30) as res:
            return res.status, ""
    except urllib.error.HTTPError as e:
        raw = e.read().decode("utf-8", "replace")
        try:
            err = json.loads(raw).get("error", {})
            return e.code, f'{err.get("status", "")} {err.get("message", "")}'[:120]
        except json.JSONDecodeError:
            return e.code, raw[:120]
 
 
def probe(label, key):
    models = call("GET", "/models?pageSize=1", key)
    gen = call(
        "POST",
        f"/models/{MODEL}:generateContent",
        key,
        {"contents": [{"parts": [{"text": "ping"}]}],
         "generationConfig": {"maxOutputTokens": 8}},
    )
    print(f"{label:<14} models={models[0]}  generate={gen[0]}  {gen[1]}")
 
 
if __name__ == "__main__":
    # 使い方: python probe.py KEY_A_ENV KEY_B_ENV
    # 引数は「キーが入っている環境変数名」。キー本体は渡さない。
    for name in sys.argv[1:]:
        value = os.environ.get(name)
        if not value:
            print(f"{name:<14} 環境変数が空です")
            continue
        probe(name, value)

引数にキーそのものではなく環境変数の名前を渡すのは、シェルの履歴にキーを残さないためです。生成のリクエストは maxOutputTokens を 8 に絞っているので、請求への影響はごく小さく抑えられます。

出力の読み方は三通りです。

modelsgenerate読み取れること
200200このキーは通っている。呼び出し側のコードやモデル ID を疑う
200403キーは認識されているが、生成だけ拒否。上の三段を順に
403403キーそのものが拒否されている。流出で無効化された可能性も

流出したキーは、公開された時点で止められることがあります。その場合のメッセージは「Your API key was reported as leaked. Please use another API key.」で、対処は AI Studio での再発行です。

別プロジェクトのキーを 1 本並べると、場所が絞れます

私がいちばん役に立つと考えているのは、診断の最後にもう 1 本、別のプロジェクトのキーを並べることです。

同じスクリプトに、動いているプロジェクトのキーと、止まっているキーを両方渡します。前者が generate=200、後者が generate=403 なら、コードでもモデル ID でもネットワークでもなく、止まっているプロジェクト側の問題だと言い切れます。ここまで絞れてから問い合わせるのと、絞らずに問い合わせるのとでは、返事の速さが変わるのだと思います。

請求の構造も、この切り分けに効いてきます。公式の請求ページによると、ティアと上限は請求アカウント単位で決まり、プロジェクトはリンクされた請求アカウントのティアを引き継ぎます。キー自体に請求設定はなく、プロジェクトの状態を引き継ぎます。また、Prepay の残高が 0 になると、その請求アカウントに紐づく全プロジェクトの全キーが同時に止まります。

つまり、「1 本だけ止まっている」なら残高ではなくプロジェクト単位の問題で、「全部止まっている」なら残高や請求アカウント単位の問題だ、という目安が立ちます。

請求の反映には時間差があります

請求を有効にした直後の確認では、もう一つ気をつけたい点があります。公式には、請求のシグナルや更新は必ずしもリアルタイムではないと書かれています。ティアの更新は通常 10 分ほどで反映され、銀行振込のような支払い方法では数日かかることもあります。

設定を変えてすぐ叩いて 403 だったとしても、そこで結論を出さないほうがよいでしょう。ただし、十数分待っても変わらず、AI Studio の Billing ページや Projects ページにも「Set up billing」や「No credits」のような案内が出ていないなら、設定の問題ではなく、プロジェクトに対する管理上の制限を疑う段階です。

フォーラムの案内でも、有料ティアでは AI Studio の Projects と Billing のページで警告バナーや異議申し立てのリンクを確認し、プロジェクトや請求アカウントが停止されている場合は Google Cloud Console から申し立てるよう示されていました。メールの受信箱にも通知が届いていることがあります。

サポートへ渡す材料

ここまで絞れたら、次は伝え方です。次の項目を、一通のメッセージにまとめておきます。

  • 失敗しているエンドポイントと、成功しているエンドポイント(models は 200、generateContent は 403)
  • エラーの本文、status、code の原文
  • スクリプトの出力(キーの名前だけを載せ、キー本体は載せない)
  • 別プロジェクトのキーでは成功していること
  • 請求を有効にした日時と、Billing ページに表示されている状態

フォーラムの投稿では、サポートプランがないと Cloud Support に連絡できないことも、行き詰まりの一因になっていました。その場合に使える入口はフォーラムでの公開質問と、AI Studio 側の案内に限られます。そのため、最初の一通に事実をすべて載せておくことが、結局はいちばん近道になります。

私が引いた線

今回の整理で、私の中に残った線引きは一つです。

403 を見たら、コードを触る前に、二本のキーを並べる。

リトライも、キーの作り直しも、そのあとです。Prepay で動かしているアプリ(壁紙の分類処理など)に、このスクリプトを小さな確認コマンドとして置いておくつもりでおります。症状が出た夜に、手の止まった状態で順番を考えなくて済むからです。

まずは、ご自分のプロジェクトのキーに対して、上のスクリプトを一度だけ実行して、models と generate がどちらも 200 で返ることを確かめておいてください。止まっていない日の出力を、一枚残しておくことをおすすめします。

シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

◈ API / SDK2026-05-17
Gemini API で画像バッチ分析の INVALID_ARGUMENT エラーを診断する
Gemini API で複数画像を一括分析するとき INVALID_ARGUMENT エラーが出て詰まった経験はありませんか。MIMEタイプ・サイズ制限・リクエスト構造という3つの原因を診断し、File API と inline data を使い分けて根本解決する実践ガイドです。
◈ API / SDK2026-05-01
Gemini API の count_tokens 推定と実課金トークン数がズレる5つの原因 — 個人開発者向け原因切り分けと再計算パターン
「count_tokens で 1,200 と出たのに、Cloud Console を見たら 4,800 トークン課金されていた」— このズレ、私もハマりました。Thinking・ツール・マルチモーダル・キャッシュの観点から原因を切り分け、見積もりを実課金に近づける再計算パターンを紹介します。
◈ API / SDK2026-09-04
ループ音源のプチノイズは、Gemini に聴かせる前に波形の端で切り分けられます
環境音のループ再生で折り返しだけがプチッと鳴る。音声理解に丸ごと投げる前に、波形の端の段差を内部の変化量で正規化してふるい分ける手順と、配布形式への変換で継ぎ目が作り直される落とし穴をまとめました。