GEMINI LABJP
ROBOTICS — The ER 1.6 preview that shut down on August 31 does have a successor. Gemini Robotics ER 2 is in public preview, in both standard and streaming variantsVIDEO — ER 2 judges success and failure from live video rather than still snapshots, which is what lets it catch spills, slips, and misalignments while a task is still runningDEADLINE — Next up is September 30, when gemini-omni-flash-preview is retired. The target is gemini-omni-1.1-flash, GA since August 27, and there are now under four weeks leftAPIKEY — Every remaining standard API key, restricted ones included, stops working during September. The replacement is an auth key bound to a Google Cloud service accountPRICE — Gemini 3.7 Flash keeps its introductory $0.75/$3.75 per 1M through December 31, then moves to $1.50/$7.50 on January 1, 2027. Any estimate crossing the year needs both figuresAUDIO — Gemini 3.5 Transcribe handles language detection across 85+ languages, speaker diarization, word-level timestamps, and custom vocabulary biasing of up to 1,000 termsROBOTICS — The ER 1.6 preview that shut down on August 31 does have a successor. Gemini Robotics ER 2 is in public preview, in both standard and streaming variantsVIDEO — ER 2 judges success and failure from live video rather than still snapshots, which is what lets it catch spills, slips, and misalignments while a task is still runningDEADLINE — Next up is September 30, when gemini-omni-flash-preview is retired. The target is gemini-omni-1.1-flash, GA since August 27, and there are now under four weeks leftAPIKEY — Every remaining standard API key, restricted ones included, stops working during September. The replacement is an auth key bound to a Google Cloud service accountPRICE — Gemini 3.7 Flash keeps its introductory $0.75/$3.75 per 1M through December 31, then moves to $1.50/$7.50 on January 1, 2027. Any estimate crossing the year needs both figuresAUDIO — Gemini 3.5 Transcribe handles language detection across 85+ languages, speaker diarization, word-level timestamps, and custom vocabulary biasing of up to 1,000 terms
Articles/API / SDK
API / SDK/2026-09-02Intermediate

Taking stock of Gemini standard API keys before September, across CI, servers, and my own machine

Standard API keys, including restricted ones, stop being accepted during September. Because this changes where keys come from rather than what they say, here is the three-layer inventory I ran across code, deploy settings, and my local machine, plus a dual-path client to switch behind.

Gemini API231Authentication2Service AccountMigration4Operations12

I opened the secrets list in my Cloudflare Workers dashboard late one evening last week. Between the Lab sites and the apps I maintain on my own, I could not say out loud how many Gemini keys I had, or where they lived. I assumed a search through the codebase would answer that. What the search returned were environment variable names — the values themselves were sitting somewhere behind a dashboard.

September arrived, and I could no longer leave that vagueness in place. Standard API keys, including the ones with restrictions attached, will stop being accepted during this month. The replacement is an auth key tied to a Google Cloud service account.

The first thing I want to say is that this is not a swap of one string for another. A string swap ends with a code search. A change of origin only ends with an inventory.

What changes is not the string, but how the key is issued

A standard key used to be self-contained. You created one in the console, put it in an environment variable, and that was the whole story. It did not much matter which project owned it. The convenience came with a cost: if the key leaked, everything that key could reach leaked with it.

An auth key is bound to a service account. Who may issue it, which project owns it, and which APIs it may call are decided on the IAM side rather than in your application configuration. Key management moves out of the app's convenience and into the project's permission design.

That is where the weight of this migration sits. Replacing a string is a job for grep. Changing how permission is held means laying out every location, issuer, and purpose side by side — otherwise you have no way of knowing what is finished and what is still outstanding.

There is a pleasant side too. Instead of distributing a fresh string to every environment on each rotation, you get to hand out narrowly scoped accounts per role. I used the occasion to separate the account that runs my article-generation batch from the one that serves inference to the published sites.

Three layers, counted separately

My first mistake was looking only at the codebase and concluding, confidently, that there were three places. There were keys in deploy settings and on my own machine that never appear in source at all. Splitting the search into these three layers is what finally made the whole picture visible.

LayerWhere to lookEasy to miss
CodeSource, tests, samples, docs, notebooksCopy-paste snippets in a README, throwaway verification scripts
Deploy settingsCI secrets, Workers and function environment variables, container argumentsWorkflows that are disabled but still configured, preview environments
Local machineShell rc files, .env, editor settings, keychainValues added for a test months ago and forgotten

Chasing this by hand was less reliable than writing it down once as a script. I run the following and keep the output as a plain text file.

#!/usr/bin/env bash
# gemini-key-inventory.sh — find where the keys actually live, in three layers
set -uo pipefail
 
PATTERN='GEMINI_API_KEY|GOOGLE_API_KEY|generativelanguage\.googleapis\.com|AIza'
 
echo "===== Layer 1: code ====="
# Skip .git and node_modules; look for key names and hardcoded traces
grep -rInE "$PATTERN" . \
  --exclude-dir={.git,node_modules,.next,dist,build,vendor} \
  2>/dev/null | head -50
 
echo
echo "===== Layer 2: deploy settings ====="
# Cloudflare Workers (run this where wrangler.toml lives)
if command -v wrangler >/dev/null 2>&1; then
  wrangler secret list 2>/dev/null || echo "  (wrangler: not authenticated, or not applicable)"
fi
# GitHub Actions repository and environment secrets
if command -v gh >/dev/null 2>&1; then
  gh secret list 2>/dev/null || echo "  (gh: no permission, or not applicable)"
  for env in $(gh api repos/:owner/:repo/environments --jq '.environments[].name' 2>/dev/null); do
    echo "  [environment] $env"
    gh secret list --env "$env" 2>/dev/null
  done
fi
 
echo
echo "===== Layer 3: local machine ====="
for f in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.profile" "$HOME/.zprofile"; do
  [ -f "$f" ] && grep -nE "$PATTERN" "$f" 2>/dev/null | sed "s|^|${f}:|"
done
# Only two levels down for .env files; going deeper never ends
find . -maxdepth 3 -name '.env*' -not -path '*/node_modules/*' -print 2>/dev/null \
  | while read -r f; do grep -lE "$PATTERN" "$f" 2>/dev/null; done
 
echo
echo "Inventory complete. Now annotate each line with who issued it — the count is not the point."

That last line turned out to matter most for me. A machine can produce the list of locations, but only you can recall the issuer and the purpose. If a key surfaces that you cannot account for, it does not belong on the migration list. It belongs on the deletion list.

Put a dual-path client in the middle before you switch

What I find frightening about a deadline migration is the moment everything stops at once. So I put a small function in front of client creation: prefer the service account path, fall back to the legacy path if it fails, and — this is the part that matters — leave a warning in the log when it falls back.

# gemini_client.py — choose an auth path, and always record which one started
from __future__ import annotations
 
import logging
import os
 
from google import genai
 
log = logging.getLogger(__name__)
 
 
def build_client() -> tuple[genai.Client, str]:
    project = os.getenv("GOOGLE_CLOUD_PROJECT")
    location = os.getenv("GOOGLE_CLOUD_LOCATION", "us-central1")
    legacy_key = os.getenv("GEMINI_API_KEY")
 
    if project:
        try:
            client = genai.Client(vertexai=True, project=project, location=location)
            # Constructing a client does not verify auth. Make one real call.
            next(iter(client.models.list()), None)
            return client, "service-account"
        except Exception as exc:  # auth, permission, and network together
            if not legacy_key:
                raise
            log.warning("Service account path unavailable: %s", exc)
 
    if not legacy_key:
        raise RuntimeError(
            "Neither GOOGLE_CLOUD_PROJECT nor GEMINI_API_KEY is set"
        )
 
    log.warning("Started on the legacy API key path; migrate before the September cutoff")
    return genai.Client(api_key=legacy_key), "legacy-api-key"
 
 
if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)
    _, route = build_client()
    log.info("gemini auth route=%s", route)

The reason for next(iter(client.models.list()), None) is that constructing the client verifies nothing. Leave it out, and the failure stays hidden until your first inference request. One call at startup means you can read the answer straight off the deploy log.

From there, searching your logs for route=legacy-api-key tells you which environments are still on the old path. I emit that line from each service at startup and glance at it in the morning. As environments dropped off the list, I deleted their old keys one at a time.

There is a related trap where a duplicated environment variable name means your replacement value never takes effect. I wrote that one up separately in the note on why the old key wins after you swap it, and reading it before the inventory may save you some time narrowing down the cause.

Where I stumbled: permission granularity, and my own machine

After creating the service account, I started out granting a broad role. I wanted something working in front of me first. The risk is forgetting to come back: you end up handing out a key with wider reach than the standard key ever had. Once the calls succeeded, I narrowed the inference account down. Granting broadly first is fine, as long as the day you narrow it goes on the calendar in the same sitting — that is the line I drew this time.

Locally, I stopped short while downloading the service account JSON. My working folder sits inside a synced Dropbox directory, and putting a key file there would leave the key in sync history as well. In the end I keep no JSON on my machine and use ADC through gcloud auth application-default login instead. Deciding just that one thing — no key files on the local machine — takes a surprising amount of worry out of the migration.

For CI, Workload Identity federation is easier to live with than pasting a JSON into a secret. With the deadline at the end of this month, though, there is nothing wrong with a two-stage approach: get it working through secrets first, then move to federation. On a deadline, building something that does not stop matters more than building the correct shape in one pass.

How I ordered the remaining days

Having an order written down makes it easier to keep going when energy runs low. Mine looks like this.

  1. Run the inventory script and write the three-layer list to a text file
  2. Annotate each line with issuer and purpose; anything unaccounted for goes on the deletion list
  3. Add the dual-path client and emit route to the log
  4. Switch the lowest-impact service to the service account first
  5. Delete old keys only for environments where legacy-api-key has disappeared from the logs

Build the state in which a key can be deleted, then delete it — not the other way around. It reads as obvious, and yet a close deadline makes the reverse tempting.

A small note on the annotation step, since it is the one most likely to be skipped. When I wrote issuer and purpose next to each line, three entries came back with nothing beside them. Two were from a weekend experiment I never finished, and the third belonged to a preview environment that had been idle for months. None of them needed migrating. Deleting keys is usually the part I put off, and having them fall out of an inventory rather than a security review made it feel like ordinary housekeeping instead of an admission of something. If your list has similar orphans, they are the cheapest progress available to you today.

One more September date sits alongside this one: the gemini-omni-flash-preview endpoint retires at the end of the month. That one is mostly a model ID swap, so clearing the key migration first — the change with the wider blast radius — should make the end of the month considerably calmer.

For today, run the inventory script once and get as far as a text file listing where your keys live. That is the point where my own shoulders finally dropped.

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 $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

API / SDK2026-08-19
gemini-2.5-flash Can Return a 404 'no longer available' While the Docs Still List No Shutdown Date
The shutdown date for gemini-2.5-flash is blank in the official deprecation table, yet 404 reports have been circulating since July. Here is why reading that column as a safety margin misleads you, and how to check your own keys.
API / SDK2026-07-29
A 17% Drop in Output Tokens Sounded Big. Then I Decomposed the Bill
Output pricing fell 16.67% and output tokens fell about 17%. Adding those numbers does not give you the savings. Here is an exact price/volume/cross-term decomposition, plus a loop measurement where the savings rate went down instead of up.
API / SDK2026-07-18
Keeping a Long-Running Managed Agent Alive Across Sandbox Recycling — Durable Checkpoints and Idempotent Resume
A Managed Agents sandbox can be recycled out from under you. Before 40 minutes of work resets to zero, we design a durable checkpoint that pushes progress outside the sandbox and an idempotent resume that never runs a side effect twice. With working SQLite code.
📚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 →