Status: public draft, updated 2026-08-18. The problem: model retirement dates live in HTML docs and emails, so every deprecation checker is a scraper with a private registry format. Ordinary software solved this layer - endoflife.date for EOL data, OSV for vulnerability feeds. This is the model version: small enough that a provider could ship it in an afternoon, useful enough that scrapers converge on it in the meantime.
A feed is a JSON document: metadata + a list of model entries.
{
"spec": "model-eol/0.1",
"publisher": "openai",
"generated": "2026-07-25T00:00:00Z",
"source": "https://developers.openai.com/api/docs/deprecations",
"models": [
{
"id": "o3-deep-research-2025-06-26",
"aliases": ["o3-deep-research"],
"announced": "2026-04-22",
"shutdown": "2026-07-23",
"replacement": "gpt-5.6-sol",
"notes": "Deep-research post-trained agent; replacement is a general model + tools, not a structural successor.",
"distributions": [
{ "via": "azure-ai-foundry", "shutdown": "2026-12-26",
"source": "https://learn.microsoft.com/en-us/azure/ai-foundry/openai/concepts/model-retirements" }
]
},
{
"id": "gpt-5.6-sol"
}
]
}| Field | Req | Meaning |
|---|---|---|
id |
yes | The exact model identifier as sent over the wire (dated snapshot preferred) |
aliases |
no | Other strings that resolve to this model (undated aliases, marketing names) |
announced |
no | Date the deprecation was announced (absent = not deprecated) |
shutdown |
no | Date calls stop working on the publisher's own API (absent = none scheduled) |
replacement |
no | A single exact model ID that resolves against an id or alias in this feed and is safe as a drop-in string substitution. This is the only field plan/apply may patch from |
replacement_options |
no | An ordered array of exact model IDs for multiple or unresolved choices. These are issue-only and need not resolve in this feed |
replacement_note |
no | Free-text guidance such as parameter requirements or provider platform alternatives |
notes |
no | Human context: structural differences, migration gotchas |
distributions |
no | Per-distributor lifecycles - the same weights on different clocks (Azure, Bedrock, Vertex). Each entry: via, optional announced/shutdown/status/date_precision, source |
date_precision |
no | Qualifies shutdown: absent or "exact" means the stated day; "earliest" is a scheduled date that cannot move earlier; "tentative" means the publisher lists a not-before retirement date without announcing deprecation |
Tentative entries carry no announced. Consumers must not treat their dates as scheduled retirements. The reference checker reports them as scheduled and never fails on them.
Dates are ISO 8601, UTC. A model with no announced and no shutdown is an
affirmative statement of "no retirement scheduled as of generated" - the absence
is data, which is exactly what scraping HTML can never give you.
distributions[].status, when present, is one of active, legacy,
extended-access, or retired. legacy means no new consumers but existing
usage works. extended-access means the distributor has announced or entered a
distinct extended-access portion of that lifecycle; it does not imply availability
after shutdown. On Amazon Bedrock, public extended access is a potentially
higher-priced portion of the Legacy period for active users and ends at EOL, so
shutdown remains the Bedrock EOL date. Bedrock still calls the encompassing
state Legacy; extended-access is this feed's finer-grained channel signal.
Private arrangements after EOL are not represented by this status. retired
means the distributor explicitly reports the model as no longer generally
available even when it does not publish an exact shutdown date.
Bedrock model-card floors are tentative distribution dates (date_precision: "tentative") without announced.
Azure maps GA/Preview/Legacy to active, Deprecated to legacy, and Retired to retired; Azure Legacy permits new deployments.
Azure OpenAI uses a resolving bare identity for single-row models only when a dated snapshot candidate is absent.
A model may contain at most one distribution for each via. Duplicate channel
records are ambiguous and fail runtime semantic validation. Draft-07 can enforce
whole-object uniqueness, but cannot express uniqueness keyed by the via field,
so model-eol validate and feed loading enforce this constraint.
replacement is a single exact model ID that resolves against an id or alias in
the same feed and is safe as a drop-in string substitution. This is the ONLY
field plan/apply may patch from. Otherwise absent.
replacement_options is an optional array of exact model IDs. Each option must
pass the identifier grammar, but feed resolution is NOT required since a target
may not be carried yet. Options are ordered as the provider lists them.
replacement_note is optional free-text guidance. It can carry parameter
requirements such as reasoning.mode: pro or prose alternatives such as Google's
platform pointers.
Replacement options and notes are issue-only. The planner never auto-patches
from options; it patches replacement exclusively.
A feed MAY include a publisher's stated minimum deprecation notice period:
"policy": {
"min_notice_days": 60,
"source": "https://platform.claude.com/docs/en/about-claude/model-deprecations"
}For an entry with neither announced nor shutdown, a policy floor means the
entry cannot become unavailable before generated + min_notice_days days,
provided the provider honors its stated policy. This is a stated policy, not a
contract or a guarantee that the provider will remain available.
The policy field is optional. Its absence means no forward claim can be made,
intentionally. OpenAI publishes no formal notice floor, so the OpenAI feed carries
no policy field.
The July 2026 evidence: OpenAI shut off o3-deep-research on July 23; Azure lists it
retiring December 26. Anthropic retired Claude 3.7 Sonnet on February 19; Bedrock
kept it to April 28. The same BOM entry has different sourcing terms per channel, and
a checker that doesn't know that will tell an Azure shop their model is dead five
months early - or tell an OpenAI-direct shop they're fine because Azure says so.
- Providers (the ask): serve the feed at
/.well-known/model-eol.json, or addannounced/shutdown/replacementto the existing models endpoint. Either kills the scraping industry overnight. - Aggregators (the interim): community datasets re-publish provider pages in this
format, with
sourceon every entry so claims are auditable. Multiple feeds compose - a checker loads provider feeds where they exist and community feeds where they don't. - Consumers: anything that can read JSON - a CI step, a dashboard, a Dependabot
clone that opens migration PRs when
shutdown - today < threshold.
Repository tools that export CycloneDX SHOULD represent a canonical model once
per lifecycle channel, with a deterministic channel-qualified bom-ref. This
keeps direct publisher and distributor clocks distinct and binds each source
occurrence to the lifecycle decision that evaluated it.
Compatibility within 0.1. The model-eol/0.1 line evolves additively: new
optional fields and new enumeration values (such as date_precision: "tentative", added in 2026-09) may appear in feeds and reports without a
version change. A consumer that validates strictly against a snapshot of these
schemas should expect to refresh that snapshot when it upgrades, and a consumer
that reads defensively should ignore fields and values it does not recognise
rather than reject the document. Removing a field or changing the meaning of an
existing value is a new spec line.
Pricing, capabilities, context windows (models.dev and friends already do this); behavior-change tracking (that's an eval suite's job, not a manifest's); signing and provenance (worth doing the moment anyone depends on this; premature today).
endoflife.date (the shape of the solution), OSV (proof that a shared schema turns N
scrapers into an ecosystem), CycloneDX ML-BOM / SPDX AI profile (where an eol field
belongs once feeds exist), and the current scrapers - llm-model-deprecation,
llmstatus.ai, llm-info, ai-model-watch-data, modelradar-data - which are the demand
signal this spec is trying to give a common target.