GEMINI LABEN
ROBOTICS — 8月31日に停止した ER 1.6 preview には後継があります。Gemini Robotics ER 2 が公開プレビュー中で、通常版とストリーミング版の2種類が提供されていますVIDEO — ER 2 の成功・失敗判定は静止画ではなく生の映像フィード上で動きます。こぼれ・滑り・位置ずれのような、実行の途中で起きる失敗を捉えられる設計ですDEADLINE — 次の期限は9月30日、gemini-omni-flash-preview の廃止です。移行先は8月27日に GA になった gemini-omni-1.1-flash で、残り4週間を切りましたAPIKEY — 残りの標準 API キーは、制限付きのものも含めて9月中に全面停止します。移行先は Google Cloud サービスアカウントに紐付く auth キー形式ですPRICE — Gemini 3.7 Flash の導入価格 $0.75/$3.75 per 1M は12月31日までです。2027年1月1日から $1.50/$7.50 になるため、年を跨ぐ見積もりは2本立てが要りますAUDIO — Gemini 3.5 Transcribe は85言語以上の言語検出、話者ダイアライゼーション、単語単位タイムスタンプ、最大1,000語のカスタム語彙バイアスに対応していますROBOTICS — 8月31日に停止した ER 1.6 preview には後継があります。Gemini Robotics ER 2 が公開プレビュー中で、通常版とストリーミング版の2種類が提供されていますVIDEO — ER 2 の成功・失敗判定は静止画ではなく生の映像フィード上で動きます。こぼれ・滑り・位置ずれのような、実行の途中で起きる失敗を捉えられる設計ですDEADLINE — 次の期限は9月30日、gemini-omni-flash-preview の廃止です。移行先は8月27日に GA になった gemini-omni-1.1-flash で、残り4週間を切りましたAPIKEY — 残りの標準 API キーは、制限付きのものも含めて9月中に全面停止します。移行先は Google Cloud サービスアカウントに紐付く auth キー形式ですPRICE — Gemini 3.7 Flash の導入価格 $0.75/$3.75 per 1M は12月31日までです。2027年1月1日から $1.50/$7.50 になるため、年を跨ぐ見積もりは2本立てが要りますAUDIO — Gemini 3.5 Transcribe は85言語以上の言語検出、話者ダイアライゼーション、単語単位タイムスタンプ、最大1,000語のカスタム語彙バイアスに対応しています
記事一覧/API / SDK
API / SDK/2026-05-24中級

Gemini File API でアップロードしたファイルが48時間後に消える問題への対処法

Gemini File API のファイルは48時間で削除される仕様です。突然 PERMISSION_DENIED や NOT_FOUND が返るようになった原因と、再アップロードを前提とした実装パターンを整理します。

Gemini API228File APIトラブルシューティング31Python44本番運用49

ある朝、AdMob のレポート取り込みに使っていた Gemini File API 経由の分類スクリプトが、見たことのないエラーで落ちていました。前日まで動いていたコードを一切変えていないのに、アップロード済みのはずの PDF を参照しに行ったところで 403 PERMISSION_DENIED が返ってきます。

原因は単純で、files.upload() で送ったファイルは アップロードから48時間で自動削除される仕様だったからです。2014年から個人開発でアプリを運用してきた廣川(@dolice)の経験でも、外部 API の TTL(Time To Live)周りはあとから刺さる落とし穴の代表格で、Gemini File API も例外ではありませんでした。

症状の見分け方、TTL に依存しないファイル受け渡しパターン、そして「再アップロード前提」で組み直すときの実装例を、実運用で詰まったポイントだけに絞って整理します。

どのエラーが出ているのかを正確に切り分ける

48時間 TTL に起因するエラーは、複数の顔を持っています。SDK のバージョンと呼び出し方によって表面化するメッセージが変わるため、最初にどの分類なのかを確定させると判断が速くなります。

代表的なものは次の3パターンです。

  • PERMISSION_DENIEDgenerateContentfile_uri を渡したときに返ってくる。すでに削除されたリソースを参照しているため権限エラー扱いになる
  • NOT_FOUNDfiles.get(name=...) で個別にステータスを確認したときに返ってくる
  • INVALID_ARGUMENT: File ... is not in an active state:稀に、削除直前の遷移中に返ってくる

特に混乱しやすいのが PERMISSION_DENIED で、認証情報を疑って API キーを差し替えても解決しません。判別のためには、まず files.get() で個別の生死を確認するのが確実です。

from google import genai
 
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
 
file_name = "files/abc123xyz"  # 以前 upload() が返した name
 
try:
    meta = client.files.get(name=file_name)
    print(meta.state, meta.expiration_time)
except Exception as e:
    print(f"file is gone or inaccessible: {e}")

expiration_time は ISO8601 で返ってきます。これが現在時刻より過去であれば、確実に TTL 失効です。

仕様としての48時間 TTL を一度きちんと押さえる

File API は Gemini API の補助エンドポイントで、Files リソースの保持期間は短期です。

  • アップロード後 48時間で自動削除される(延長 API は提供されていない)
  • 削除されたあとに file_uri を参照すると PERMISSION_DENIED が返る
  • 1ファイル上限は 2GB、プロジェクト全体で 20GB まで保持できる
  • 同じバイナリでも files.upload() を呼ぶたびに別 name のリソースになる

つまり「一度アップロードして使い回す」運用は最大2日間しか保たない、と最初から割り切るのが安全です。長期保存したいなら Google Cloud Storage に置いて Vertex AI 経由で参照するなど、別のサービスを組み合わせる前提で設計するのが現実的です。

詳細は Files API 公式ドキュメントに明記されていますが、移行ガイドや古い記事を見ながら実装していると、TTL の存在を見落としやすい部分でもあります。

対処1:呼び出しの直前に毎回アップロードする

もっとも単純で確実な対処は、generateContent の直前で必ず files.upload() を呼び直すことです。バッチで何百件も処理する場合は無駄が増えますが、1日数回〜数十回程度の呼び出しなら、TTL のことを完全に忘れられる安心感が勝ちます。

from pathlib import Path
from google import genai
from google.genai import types
 
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
 
def classify_pdf(pdf_path: Path, prompt: str) -> str:
    """毎回アップロード → 分類 → 削除 の最小サイクル"""
    uploaded = client.files.upload(file=pdf_path)
    try:
        result = client.models.generate_content(
            model="gemini-2.5-flash",
            contents=[uploaded, prompt],
        )
        return result.text
    finally:
        # 待たずに即削除すれば 20GB のプロジェクト枠も浪費しない
        client.files.delete(name=uploaded.name)

finallyfiles.delete() を呼ぶのは、容量枠を意識した小さな配慮ですが、長期間稼働するワーカーでは効いてきます。個人開発で複数アプリを掛け持ちしている場合、ここを抜くと半月ほどで容量上限に当たることがあります。

対処2:20MB 未満なら inlineData にフォールバックする

PDF や画像が小さい場合は、そもそも File API を経由せず inline_data として直接埋め込んでしまう手があります。HTTP リクエスト本体が 20MB 以内に収まるなら、API 側の制約的にもこちらで通ります。

import base64
from pathlib import Path
from google import genai
from google.genai import types
 
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
 
def classify_small_pdf(pdf_path: Path, prompt: str) -> str:
    data = pdf_path.read_bytes()
 
    part = types.Part.from_bytes(
        data=data,
        mime_type="application/pdf",
    )
 
    result = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=[part, prompt],
    )
    return result.text

inline 方式の利点は TTL に縛られないことと、リトライ時もファイル状態を気にしなくていいことです。一方でリクエストサイズが膨らむので、3MB を超えるあたりからレイテンシの増加が目立ちます。サイズに応じてスイッチする小さなルーターを挟むと使い分けやすくなります。

THRESHOLD_BYTES = 20 * 1024 * 1024  # 20MB
 
def smart_classify(pdf_path: Path, prompt: str) -> str:
    if pdf_path.stat().st_size < THRESHOLD_BYTES:
        return classify_small_pdf(pdf_path, prompt)
    return classify_pdf(pdf_path, prompt)

対処3:再利用したいなら期限を見て再アップロードする

同じファイルを1日に何度も使い回したい場合は、自前で「期限管理 + 再アップロード」を入れるのが現実的です。Redis や SQLite に (local_path, remote_name, expires_at) のテーブルを持っておき、呼び出し前に有効性を確認します。

import datetime as dt
import sqlite3
from pathlib import Path
from google import genai
 
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
SAFETY_MARGIN = dt.timedelta(hours=2)  # 2時間早めに更新
 
def get_or_refresh(conn: sqlite3.Connection, local_path: Path) -> str:
    row = conn.execute(
        "SELECT remote_name, expires_at FROM files WHERE local_path = ?",
        (str(local_path),),
    ).fetchone()
 
    now = dt.datetime.now(dt.timezone.utc)
 
    if row:
        remote_name, expires_at = row
        if dt.datetime.fromisoformat(expires_at) - SAFETY_MARGIN > now:
            return remote_name
        # 期限が近いか過ぎているので破棄して再アップロード
        try:
            client.files.delete(name=remote_name)
        except Exception:
            pass
 
    uploaded = client.files.upload(file=local_path)
    conn.execute(
        "INSERT OR REPLACE INTO files VALUES (?, ?, ?)",
        (str(local_path), uploaded.name, uploaded.expiration_time.isoformat()),
    )
    conn.commit()
    return uploaded.name

2時間の安全マージンを取っているのは、ちょうど期限ぎりぎりで参照すると遷移中の INVALID_ARGUMENT が出ることがあるからです。バッチが午前2時に走る運用なら、その時刻をまたぐタイミングで失効しないかを確認しておくと安心です。

つまずきやすいポイントを最後にまとめる

実運用に持ち込む前に、次の点を必ず確認しています。

  1. expiration_timeUTC で返ってくるので、ローカルタイムと混ぜて比較しないこと
  2. files.list() は最大100件までしか返さないため、page_token で全件走査する処理を入れること
  3. SDK のバージョンによって state のフィールド名が ACTIVE / PROCESSING / FAILED で微妙に違うので、enum 比較ではなく文字列マッチで書いておくほうが壊れにくい
  4. CI のテストでは テスト用の小さな PDF を都度アップロードして使い、レコードを共有しないこと

廣川(@dolice)が個人運用しているアプリでは、App Store レビュー CSV を週次で分類する処理に File API を組み込んでいますが、最終的には「TTL を信用せず毎回アップロード」がいちばん事故が少ない結論になりました。容量や帯域に余裕がある今のプランでは、シンプルさが運用コストを下げてくれます。

同じところで詰まっている方の助けになれば幸いです。お読みいただきありがとうございました。

シェア

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

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

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

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

関連記事

API / SDK2026-05-15
Gemini API Embedding で詰まった3か所 — 壁紙アプリのカテゴリ自動分類で踏んだエラーと対処
Gemini API の Embedding をカテゴリ自動分類に組み込んで踏んだ INVALID_ARGUMENT・429・精度不足の3点を、次元の固定・切り詰め後の正規化・チェックポイント付きバッチ・recall@5 の計測台という形で整理した記録です。
API / SDK2026-05-11
Gemini 3.2 API に切り替えたら動かなくなった — 私が実際にぶつかったエラーパターンと対処法
Gemini 3.2 API への移行後に頻発するエラー5種を実例コード付きで解説。モデルID誤り・レート制限・コンテキスト超過・ストリーミング断裂・Function Calling スキーマ違反の診断と修正方法を紹介します。
API / SDK2026-04-28
Gemini API が社内プロキシ・SSL検証エラーで接続できないときの対処法
個人PCでは動いた Gemini API が、会社支給のマシンや社内ネットワークから繋がらない。プロキシ環境変数・SSL検証エラー・証明書バンドルの3層を切り分けて解決する実践的なトラブルシューティングです。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →