I opened the Gemini deprecations page to plan the order of a migration, went looking for the gemini-2.5-flash row, and stopped at the shutdown column.
There is no date there. It says No shutdown date announced. Same for gemini-2.5-pro and gemini-2.5-flash-lite. The page was last updated on August 13, 2026.
Meanwhile, the developer forum has a thread from July 9 reporting that a call to gemini-2.5-flash came back with a 404 and the message This model models/gemini-2.5-flash is no longer available (thread). A separate report describes gemini-2.5-pro responding that it is no longer available to new users.
Blank shutdown date, failing calls. These two facts do not actually contradict each other. They only look like a contradiction if you read the shutdown column as a promise that the model stays up until that day.
An empty shutdown column is not the same as "still available"
Right above the tables, the deprecations page carries a note: the shutdown dates listed are the earliest possible dates on which a model might be retired, and the exact date will be communicated separately.
So the table gives you a floor, not a ceiling. Reading it as "we are fine until October 16" buys you confidence that nothing actually backs. And the dates for gemini-2.5-flash and gemini-2.5-pro that used to sit in those cells are now shown as unannounced. A date disappearing is not the same as a deadline moving further away.
Here is what I could read off the table as of the August 13 update.
| Model | Shutdown date in the docs | Recommended replacement |
|---|---|---|
gemini-2.5-pro | Not announced | None listed |
gemini-2.5-flash | Not announced | None listed |
gemini-2.5-flash-lite | Not announced | None listed |
gemini-2.5-flash-image | October 2, 2026 | gemini-3.1-flash-image-preview |
gemini-2.0-flash / gemini-2.0-flash-001 | June 1, 2026 (already passed) | gemini-3.6-flash |
The row that unsettles me most is gemini-2.5-flash-image, whose recommended replacement is gemini-3.1-flash-image-preview. Read plainly, that asks you to move from a GA model to a preview one. If your policy is to keep preview models out of production defaults, you cannot simply follow it. The inversion I wrote about in the strongest Gemini being in preview while the cheap one is GA shows up on the deprecation side too.
"no longer available" and "no longer available to new users" are different failures
Two distinct messages appear in the reports, and the difference matters when you are isolating a problem.
The first one, no longer available, means the endpoint itself is closed. Any key should get the same answer.
The second one, no longer available to new users, depends on who is calling. Projects with a history of using that model keep working, while a project or key created more recently gets turned away. That asymmetry is the uncomfortable part.
As an indie developer looking after several apps and sites alone, I end up with one key per purpose, created at very different times. Production, staging, and scheduled jobs each have their own.
In practice it shows up like this: the long-lived production project works, but the staging project you spun up last month, or the key you minted for CI, returns a 404. Same code, same model ID, different outcome.
At that point every suspect looks like it lives in your code. You check the SDK version, you check the region, you re-read how the request is assembled, and the afternoon is gone. I have since moved one rule to the front of my checklist: when identical code behaves differently per environment, suspect the key before the code. If model availability is scoped per project, no amount of reading the request builder will produce an answer.
Find out what your own keys can see, in about five minutes
Stop guessing and call models.list once per key. If you would rather not add a dependency, curl is enough.
curl -s "https://generativelanguage.googleapis.com/v1beta/models?pageSize=200" \
-H "x-goog-api-key: YOUR_API_KEY" \
| grep -o '"name": "models/[^"]*"' \
| sed 's|.*models/||; s|"$||' \
| sortIf gemini-2.5-flash is missing from that list, the key can no longer see it, and the migration is no longer something you can leave for later.
Now the part that actually pays off. One key is not enough. Run it against production and against your CI or staging key, then diff the two. A model that only one side can see is a model that will eventually disappear from the other side as well.
#!/usr/bin/env python3
"""Compare models.list across a production key and a CI key, and print the difference."""
import json
import os
import sys
import urllib.request
ENDPOINT = "https://generativelanguage.googleapis.com/v1beta/models?pageSize=200"
def list_models(api_key):
names = set()
url = ENDPOINT
while url:
req = urllib.request.Request(url, headers={"x-goog-api-key": api_key})
with urllib.request.urlopen(req, timeout=30) as res:
body = json.load(res)
for model in body.get("models", []):
if "generateContent" in model.get("supportedGenerationMethods", []):
names.add(model["name"].replace("models/", "", 1))
token = body.get("nextPageToken")
url = ENDPOINT + "&pageToken=" + token if token else None
return names
def main():
prod_key = os.environ.get("PROD_API_KEY")
ci_key = os.environ.get("CI_API_KEY")
if not prod_key or not ci_key:
print("Set PROD_API_KEY and CI_API_KEY in the environment", file=sys.stderr)
return 2
prod = list_models(prod_key)
ci = list_models(ci_key)
only_prod = sorted(prod - ci)
only_ci = sorted(ci - prod)
print("Models visible to the production key: %d" % len(prod))
print("Models visible to the CI key: %d" % len(ci))
print("Production only:", ", ".join(only_prod) if only_prod else "none")
print("CI only:", ", ".join(only_ci) if only_ci else "none")
return 1 if (only_prod or only_ci) else 0
if __name__ == "__main__":
raise SystemExit(main())It filters on generateContent because mixing embedding and audio models into the same set makes the diff harder to read. It exits with code 1 when a difference exists, so you can drop it into a scheduled job and only hear from it on the days something moved.
Walking the pages properly matters more than it looks. Even with a generous pageSize, the catalog keeps growing, and there is no guarantee a single page holds everything.
Model IDs survive outside your code
Fixing the call sites was not the end of it. In my own repositories, the leftovers were mostly outside the code path.
grep -rInE 'gemini-(2\.0|2\.5)[a-z0-9.-]*' . \
--include='*.py' --include='*.ts' --include='*.js' --include='*.kt' --include='*.swift' \
--include='*.json' --include='*.yaml' --include='*.yml' --include='*.toml' --include='*.md'Running that against my own repositories surfaced hits in places like these:
- Default values in config files, where the code reads an environment variable but the fallback is still an old ID
- Recorded test responses with an old model ID baked in, so the suite stays green no matter what the live endpoint does
- Code samples written for readers, plus internal runbooks
- App Store and Google Play descriptions, and feature pages that say in prose which model powers a feature
The second one is the quiet problem. A test replaying a recorded response says nothing when the real endpoint closes. I wrote about that shape from the freshness-gate side in green tests and dead production.
The fourth is not a technical break, but it keeps handing readers an outdated assumption. Adding one line to the migration checklist for searching prose, not just code, is cheap insurance.
The other date, so you do not choose a replacement twice
Choosing a replacement in a hurry usually means doing the same work again shortly afterward, because another date is already on the calendar.
gemini-3.7-flash reached general availability on August 13. The published introductory pricing of $0.75 per million input tokens and $3.75 per million output tokens runs through December 31, 2026, and the announced rate from January 1, 2027 is $1.50 / $7.50. Deciding on today's unit price alone means redoing the arithmetic in the new year.
So it saves effort to make the 2.5 migration decision alongside two questions:
- Which workloads hurt most if they stop right now? Conversational, batch, image, and embedding paths fail in very different ways.
- Will each workload still be running after January 2027, or does its job finish this year?
Work that finishes this year can take the introductory price at face value. Work that keeps running needs to be compared at the doubled rate, or you will migrate a second time in January. I worked through that trade-off from the rework-cost angle in routing between Gemini 3.7 Flash and 3.1 Pro by rework cost.
One more thing worth deciding in advance: how you treat the 404 once it starts arriving. A retired-model 404 is not the kind of failure that resolves if you wait. Stacking exponential backoff on top of it only accumulates time spent waiting for something that will never succeed. How that waiting failed to appear in the success-side statistics is covered in retrying a retired model never showed up in success latency.
If you do one thing today, call models.list with your production key and your CI key and compare the two lists. No difference means you can go back to watching the table for a while. A difference means you have a signal that arrived ahead of the official announcement.