◉GEMINI LABJP
●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — The old Deep Research agent shut down on 10/23. If calls fail, move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●CLI — Gemini API CLI (gemini-api) is out in beta (10/27): a zero-dependency client for agents and CI●STORE — New paid projects now save Generate Content requests by default (store on) as of 10/26●NEW — Stop Re-Reading Every Crash Report: Gre…●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — The old Deep Research agent shut down on 10/23. If calls fail, move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●CLI — Gemini API CLI (gemini-api) is out in beta (10/27): a zero-dependency client for agents and CI●STORE — New paid projects now save Generate Content requests by default (store on) as of 10/26●NEW — Stop Re-Reading Every Crash Report: Gre…
Articles/API / SDK
◈ API / SDK/2026-10-30Intermediate

My ADK Agent Never Ran and Said 'API key expired' — Reading the Reason Code Gave Me a Triage Order

When ADK returns 400 INVALID_ARGUMENT 'API key expired. Please renew the API key.' on every run, check this order before assuming the key expired: read the reason code, compare shell and .env keys by their last four characters, then test the key alone over REST.

ADK3Gemini API254API key4troubleshooting97environment variables400 INVALID_ARGUMENT

It was late, and I had just wired up a small agent with ADK. I ran adk run, and the first thing on screen was 400 INVALID_ARGUMENT followed by "API key expired. Please renew the API key."

I had created that key a few days earlier. Still, I've taken that kind of message at face value before: regenerate the key, paste it in, hit the same error in the same place, and slump a little. As an indie developer who keeps a handful of apps and sites running, I've lost more than one evening that way.

The thing I'd like to say first is that the message may not be naming the cause at all. The part worth reading is the reason field in the same response.

To be clear, this is not a record of reproducing an outage on my machine. It's a triage order built from a GitHub report in the gemini-cli repository (#24831, opened April 7, 2026) and Google's official API key page. That report has no comments and no fix, so I can't tell you "this is the answer." I can only tell you where I'd start so I don't wander.

Taking the reported error apart

The report shows this:

google.genai.errors.ClientError: 400 INVALID_ARGUMENT
message: "API key expired. Please renew the API key."
details: reason = API_KEY_INVALID  (generativelanguage.googleapis.com)

The message says "expired," but the reason says API_KEY_INVALID. I couldn't find any statement on the official key page that standard keys have an expiry date. So I treat the reason as the more reliable signal: the server could not accept the string it was handed as a valid key.

That leaves two doors. Either the key itself is bad, or what was handed over isn't what I think it is.

Check what was handed over first

Regenerating a key changes nothing if the path that delivers it stays the same, so I look at the delivery before the key.

Google's page says the client libraries read GEMINI_API_KEY or GOOGLE_API_KEY, and if both are set, GOOGLE_API_KEY wins. ADK's own docs tell you to put GOOGLE_API_KEY in a .env file in the agent folder.

Here's the trap: an old key still exported in the shell as GOOGLE_API_KEY. You write the new key into .env, and nothing on screen tells you which one is read. ADK's docs don't say how .env values are ordered against existing shell variables, so I don't guess — I look.

Showing where each key comes from, without printing it

I don't want keys in my terminal history or logs, so this script prints only the last four characters and the length, side by side for the shell and the .env file.

import os
import sys
from pathlib import Path
 
 
def tail(value):
    """Return only the last 4 characters. Never print the whole key."""
    if not value:
        return "(unset)"
    value = value.strip().strip('"').strip("'")
    return f"…{value[-4:]} (length {len(value)})"
 
 
def read_dotenv(path):
    result = {}
    if not path.exists():
        return result
    for line in path.read_text(encoding="utf-8").splitlines():
        line = line.strip()
        if not line or line.startswith("#") or "=" not in line:
            continue
        key, _, val = line.partition("=")
        result[key.strip()] = val
    return result
 
 
agent_dir = Path(sys.argv[1] if len(sys.argv) > 1 else ".")
env_file = read_dotenv(agent_dir / ".env")
 
print("shell  GOOGLE_API_KEY :", tail(os.environ.get("GOOGLE_API_KEY")))
print("shell  GEMINI_API_KEY :", tail(os.environ.get("GEMINI_API_KEY")))
print(".env   GOOGLE_API_KEY :", tail(env_file.get("GOOGLE_API_KEY")))
print(".env   GEMINI_API_KEY :", tail(env_file.get("GEMINI_API_KEY")))

Run it as python3 probe_key.py ./my_agent, pointing at the folder that holds .env. Any line whose last four characters don't match the key you see in AI Studio is where the old key lives. An unexpected length usually means a stray quote or newline came along when you pasted.

If none of the four lines matches the key you created recently, the problem isn't ADK — the key you pasted is simply a different one. That alone closes half the search.

Take ADK out and test the key alone

Next I separate ADK from the key by sending the smallest request I can, a model list, with the same key.

# Read the key from the environment so it never lands in shell history
curl -s -o /dev/null -w "%{http_code}\n" \
  -H "x-goog-api-key: ${GOOGLE_API_KEY}" \
  "https://generativelanguage.googleapis.com/v1beta/models"

A 200 means the key is alive, and suspicion moves to ADK's configuration or to a different variable being read. A 400 or 403 means the key's own state is the problem.

When the state is the problem, the official page points to a few candidates:

  • Unrestricted standard keys can be rejected by the Gemini API, and dormant unrestricted keys are blocked and tagged "Blocked." Create a new key or use one restricted to the Gemini API.
  • If you restricted a key shared with other Google APIs, Gemini requests using it will fail. Make a separate key for Gemini.
  • If a key doesn't show up in AI Studio, the existing Cloud project may need to be imported first.

While I was fixated on the word "expired," it never occurred to me to look at restrictions and key status. Read the reason, then check the status list — changing the order changed where I went.

The line I drew

What I changed was the order in which I read errors. For a long time I took the message as the cause and ran straight to regenerating keys, and the results were poor. Now I stop at three steps:

  1. Read the reason in the response, and treat the message text as a hint.
  2. Compare last-four fingerprints to see which path delivered which key.
  3. Remove ADK and test the key alone over REST.

The message is a hint, the reason is evidence, and regenerating the key comes last. I try to hold that line even on nights when I'm tired.

What to do next

Run the four-line fingerprint once in your agent folder. Seeing four characters is often enough to decide which key to doubt. For related notes: how I traced key precedence in the CLI is in Gemini CLI Kept Saying 'API key not valid' After I Gave It a New Key, the GOOGLE/GEMINI variable mix-up is in the environment variable confusion article, and swapping keys without downtime is in zero-downtime key rotation.

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 →

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-10-30
An 'API key expired' Error Doesn't Always Mean Expired. Check Which Key Is Actually Sent Before You Renew It
A 400 with API_KEY_INVALID from the Gemini API cannot tell you whether the key expired or something else is wrong. A small diagnostic script checks env-var precedence and stray quotes first, run here against dummy keys.
◈ API / SDK2026-10-14
When Your config Raises "Extra inputs are not permitted", Check the Spelling of Every Key
If a google-genai call fails with a pydantic ValidationError (extra_forbidden) the moment you pass a config, it is almost always a one-letter typo. Here is the output I reproduced, a small function that checks keys before the call, and how to tell why response.text came back as None.
◈ API / SDK2026-09-01
Your New Gemini API Key Never Took Effect, and Load Order Wasn't the Reason
When several sources supply the same environment variable, swapping your key can leave the old value in place. Here is what four load orders actually produced, and a ledger that records which source won.