GEMINI LABJP
NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20NANOLITE — Nano Banana 2 Lite is here: Google's fastest and most cost-efficient Gemini Image model, made for running lightweight image generation cheaplyOMNIFLASH — Gemini Omni Flash is in public preview, a natively multimodal model that lets enterprises and developers build custom, dynamic video workflowsAGENTS — Managed Agents expand with background: true for async server-side runs and polling, remote MCP server integration, and refreshing credentials across interactionsMEMORY — The Memory Bank IngestEvents API is generally available, decoupling event ingestion from memory generation so you can stream content continuouslyTHROUGHPUT — Provisioned Throughput now lets you submit up to seven pending orders for the same model and regionDEPRECATE — Image generation models shut down on August 17, and the Grok 4.1 family on the Gemini Enterprise Agent Platform on August 20
Articles/API / SDK
API / SDK/2026-06-03Advanced

Reconciling Orphaned Gemini Files API Uploads Across a Fleet of Apps

Files API uploads quietly expire after 48 hours. Here's how I keep orphaned files and quota under control across six apps, using reconciliation against my own database and a scheduled cleanup job — written up as production notes from running wallpaper apps.

Gemini API192Files API4Production32Cost Optimization13Indie Development12

Premium Article

One morning my wallpaper-classification batch was throwing File ... is not found over and over and had stalled. Images I was certain I had uploaded the night before had simply vanished from Gemini's Files API. The reason was immediate: files uploaded to the Files API expire automatically 48 hours after upload. I "knew" that, in the sense that I had read it, but I had never actually built that fact into my design.

I have been building apps on my own since 2014, mostly wallpaper and relaxation titles, and they have reached somewhere around 50 million downloads in total. Lately I let Gemini's multimodal capabilities handle the category classification of those wallpaper assets, uploading images for six apps in daily batches. The more volume I pushed through, the more this "expires in 48 hours" behavior — and the state management around it — became the pressure point of the whole operation. This article is not about the one-off question of "what do I do when a file disappears." It is about how to keep orphaned files and quota consumption under control across a fleet of apps, with the reconciliation and cleanup design written out in code.

Get the Files API lifecycle exactly right

Start with the facts that anchor the design. Run something to production on a fuzzy understanding of these and you will get hurt later.

Files uploaded to the Files API come with a few constraints you cannot negotiate. First, retention is 48 hours, and it cannot be extended. Second, there is a cap on total storage per project; exceed it and new uploads are rejected. Third, a freshly uploaded file sits in the PROCESSING state and cannot be used for inference until it becomes ACTIVE. For large files such as video, that processing wait can be long enough to matter.

In other words, the Files API is not persistent storage. It is a temporary hand-off area that is valid for 48 hours. Miss that, and you end up saving the uri of an uploaded file in your own database to reuse it, only to watch everything break two days later. That is exactly what I did first.

The code to check state looks like this. The part that waits for ACTIVE after upload is non-negotiable in production.

import time
from google import genai
 
client = genai.Client(api_key="YOUR_GEMINI_API_KEY")
 
def upload_and_wait(path: str, timeout_s: int = 120):
    """Upload and wait until ACTIVE. Using it while PROCESSING will fail."""
    f = client.files.upload(file=path)
    deadline = time.time() + timeout_s
    while f.state.name == "PROCESSING":
        if time.time() > deadline:
            # Don't swallow stuck files; let the caller decide
            raise TimeoutError(f"{f.name} stuck in PROCESSING")
        time.sleep(2)
        f = client.files.get(name=f.name)
    if f.state.name != "ACTIVE":
        raise RuntimeError(f"{f.name} ended in state {f.state.name}")
    return f

The important parts are not handing a PROCESSING file to inference, and setting a timeout so you never spin forever. Early on I waited with sleep alone, and a single file stuck in processing kept my whole batch frozen until morning.

Why "upload and forget" falls apart

A common pattern in solo development is the naive assumption that once you upload, the API will take care of the rest. It is insidious precisely because small experiments never surface the problem.

My first version looked roughly like this. Look at it as the cautionary example.

# Before: upload and never track state
def classify_wallpaper(path: str):
    f = client.files.upload(file=path)
    resp = client.models.generate_content(
        model="gemini-2.5-flash",
        contents=[f, "Classify this image into one of 30 categories"],
    )
    return resp.text
    # f is never referenced again. Never deleted. Never recorded.

This has three problems. First, the uploaded file is never deleted, so it wastes storage for a full 48 hours. Second, there is no record of which file was uploaded when, so you can never answer "how many files are currently sitting on the Files API side." Third, when classification fails partway through, the already-uploaded file is left dangling. That is an orphaned file.

At a few dozen a day, the damage looks small because expiry cleans up after 48 hours. But once you are uploading several thousand a day across six apps, the story changes. A design that only waits for expiry will pin against the storage cap and start rejecting new uploads, and more importantly, the state of "I don't know what's still out there" becomes a constant source of operational anxiety. My app revenue depends almost entirely on AdMob, so I am sensitive to cost down to the yen. Resources running in the background that I cannot account for felt wrong on their own.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
A reconciliation script that detects orphaned files by comparing the Files API against your own database, built around the 48-hour expiry
How to account for quota and cost once uploads reach thousands per day, and why you should delete actively instead of waiting for expiry
Naming conventions and a cleanup job that let one person run six apps in parallel without losing track of state
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

API / SDK2026-07-11
When Gemini's Context Cache Quietly Expires Mid-Run: A TTL Guard for Pipelines That Pause
When a nightly batch or a retry backoff pauses your pipeline, Gemini's explicit context cache can expire on the wall clock while nothing errors out, sending later calls back to full-token billing. Here is a small lease guard that decides whether to re-arm or run uncached based on cost.
API / SDK2026-06-28
Read Video with Timestamps in the Gemini API: Pull Just the Scene You Need
Hunting for 'where was that step?' in a screen recording or app demo is a chore. Here is how to use Gemini API video understanding to pull just the right scene with timestamps, plus a design that keeps tokens down with FPS and resolution.
API / SDK2026-06-28
When Gemini × Qdrant Hybrid Search Was Quietly Losing Recall — Field Notes on Instrumenting RRF Weights and Sparse-Vector Drift
Run Gemini embeddings with Qdrant hybrid search in production and your dashboards stay green while recall quietly slips. These field notes show how to catch it with measurement — RRF weights, sparse-vector drift, missing payload indexes — and protect it with a quality budget.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →