Skip to content

Checking Model Name Strings Before a Migration

9 min read · updated August 11, 2026

Two strings that both mean “the current model” can select different weights, different defaults and a different bill. The difference between them is whether the string is an alias or a dated snapshot, and nothing in the request tells you which one you sent.

An alias is a pointer; a snapshot is a contract

Every major provider publishes its models under two kinds of name. One is short and unversioned — gpt-4o, claude-sonnet-4-5, gemini-2.5-pro. The other carries a date — gpt-4o-2024-08-06, claude-sonnet-4-5-20250929. They are not two ways of writing the same thing. The short name is a pointer the provider maintains and repoints when it ships a new snapshot of that model. The dated name identifies one set of weights and one serving configuration, and it does not move.

That gives each name exactly one failure mode, and they are opposites. An alias never stops resolving, so it never wakes you up — it changes under a running system and the first symptom is a behaviour report from somebody downstream. A snapshot never changes behaviour, so it never surprises you — it is retired on a published schedule and the first symptom is a hard error on every request at once. There is no third name that neither drifts nor expires, and choosing not to think about it does not get you one; it gets you whichever your config happens to contain.

The consequence people meet late is that an alias also removes your ability to roll back. If claude-sonnet-4-5 repoints on a Tuesday and your output quality drops on the Tuesday, the string you need in order to go back is the snapshot you were being served on Monday — and if you never recorded it, you are guessing from a changelog. This is worth engineering around, because both major APIs hand you the answer for free: the response body echoes the model that actually served the request. OpenAI’s chat completion object carries a top-level model field, Anthropic’s message object carries model the same way, and both generally report the resolved snapshot rather than the alias you asked for. Log the response’s value, not the request’s. It costs one field in your request log and it converts “something changed” into a date.

The specific strings above are current at the time of writing and are the fastest thing on this page to go stale. The mechanism — short name repoints, dated name expires — is the part to carry forward. See silent model updates for what the drift looks like from the application side.

The same model, four spellings

A model identifier is not a property of the model. It is a property of the host you reached it through, and the same weights are addressed differently on each one. If your service runs in more than one place — a US deployment on one host, an EU deployment on another, a local development stack on a third — a single MODEL environment variable is not portable between them, and the config that looks duplicated is not.

  • Direct vendor APIs use the alias and snapshot pair described above, with the date appended as a bare YYYYMMDD after a hyphen on Anthropic and as YYYY-MM-DD on OpenAI. Two different date formats, in the same position, on two APIs you may well call from the same service.
  • Google Vertex AI separates the version with @ rather than a hyphen — the same snapshot appears as claude-sonnet-4-5@20250929. A string copied from the vendor documentation into a Vertex client is a 404, and the diff between the working and the broken value is one character.
  • AWS Bedrock prefixes a vendor namespace and appends its own version — anthropic.claude-3-5-sonnet-20241022-v2:0 — where the trailing :0 is Bedrock’s model version and has nothing to do with the model family’s version. Cross-region inference profiles prepend a region group, so the same model is us.anthropic.claude-... in one account and something else in another, and an identifier that works in one region genuinely does not exist in the next.
  • Azure OpenAI does not take a model name in the body at all in the classic path shape: the URL carries a deployment name you chose yourself, as in /openai/deployments/<your-name>/chat/completions, plus an api-version query parameter. The value in your config is an artefact of your own subscription and conveys nothing about which model or snapshot is behind it. Which model that deployment points at is a setting in a portal, changeable by somebody who is not reading your repository.
  • Open-weights hosts generally use a org/repo pair, sometimes with a quantisation or revision suffix. Here the pointer/snapshot problem reappears as a git revision: a repo name without a pinned revision is an alias.

Azure is the case worth dwelling on, because it inverts the alias-versus-snapshot choice rather than participating in it. The deployment name is stable by construction, so your config never breaks; what changes is the thing behind it, silently, through a control plane your deploy pipeline does not touch. You get the alias’s failure mode with none of the alias’s visibility.

Where the string quietly means something else

  • The date is a snapshot date, and it is not sortable across families. claude-3-5-sonnet-20241022 has a later substring than a hypothetical claude-sonnet-4-5-20250101 in neither position that a naive sort would agree on, and any code that picks “the latest model” by lexical comparison of identifiers will eventually pick a two-generation-old one. Family version and snapshot date are independent axes. Sort by neither; choose explicitly.
  • Family numbers are not decimals. Parsing 4.5 out of a name and comparing it as a float makes 4.5 greater than 4.10. Model families are versioned like software, not like measurements.
  • Substring checks catch the sibling you did not mean. A guard written as “name starts with gpt-4” matches the mini tier. A pricing table keyed on a prefix will bill a -mini, -haiku or -flash request at the flagship rate, and a capability check written the same way will send a request the cheaper sibling cannot serve.
  • Preview and experimental suffixes have the shortest lives. A string containing preview or exp is explicitly not covered by whatever deprecation notice period the stable models get. It is fine in a spike and wrong in a fallback chain.
  • Whitespace is not trimmed for you. Model matching is exact on every API here. A value read from a file with a trailing newline, or pasted with a non-breaking space, produces a model-not-found error whose message shows a string that looks correct.

What a wrong string actually does

The loud case is a 404 with an error code of model_not_found or a message naming the model you sent, and it is the good outcome: it happens on the first request in staging and nothing reaches a user. The model-not-found error page covers the diagnosis.

The quiet cases are the expensive ones. A typo that lands on a real cheaper sibling does not error at all — it serves worse answers at a lower cost, indefinitely, and looks like a quality regression with no deploy attached. A model that exists but is not enabled for your organisation returns an access error that reads like a not-found, sending you to check spelling instead of entitlements. And a fallback chain assembled from strings that were all correct eighteen months ago fails as a unit: the primary 404s, every fallback 404s, and your circuit breaker reports a total provider outage that is really a config expiry. That last one is worth a specific test — see testing fallback order across providers.

What to check before the config change ships

The single highest-value habit is to stop reading model names out of documentation and start reading them out of the account you will actually call from. Both major APIs expose a list-models endpoint (GET /v1/models on each), and what it returns is scoped to your credentials, so it answers the entitlement question and the spelling question at once.

# The list is per-account, which is the point: docs are global, access is not.
curl -s https://api.openai.com/v1/models \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
| jq -r '.data[].id' | sort | grep '^gpt-4o'

# And check what actually served a request, rather than what you asked for:
curl -s https://api.openai.com/v1/chat/completions \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o","max_completion_tokens":5,
       "messages":[{"role":"user","content":"ping"}]}' \
| jq '{requested:"gpt-4o", served:.model, fingerprint:.system_fingerprint}'

Run that against the target account and region, not a personal key. Then check the same string in the four other places it lives — the fallback list, the eval configuration, the test fixtures and the per-tenant overrides — because a rotation that updates one of them produces a service whose primary path and its failure path disagree about which model it runs. The rotation checklist walks those places in order. If the string is changing family rather than snapshot, the behaviour question is separate and comes after: what a version bump actually changes.