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:
- Read the
reasonin the response, and treat the message text as a hint. - Compare last-four fingerprints to see which path delivered which key.
- 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.