\"temperature is deprecated for this model\" — What Changed and How to Fix It
By Promptster Team · 2026-08-18
You upgraded a model ID and every request started failing:
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "`temperature` is deprecated for this model."
}
}
Nothing else changed. The same request body worked yesterday on the previous model.
This is a deliberate breaking change, it affects more models than most changelogs make obvious, and the way teams usually detect "which models are affected" has a trap in it that we walked straight into.
Why it happens
Newer reasoning-first models don't expose sampling controls. On Anthropic's Claude 5 family and the Opus 4.7+ line, temperature, top_p, and top_k are removed, not ignored — sending any of them returns a 400.
The reasoning is that these models control their own generation depth through adaptive thinking and an effort setting. A caller-supplied temperature would fight that.
Verified against the live API:
| Model | temperature: 0.7 |
|---|---|
claude-opus-5 |
400 |
claude-sonnet-5 |
400 |
claude-fable-5 |
400 |
claude-opus-4-8, claude-opus-4-7 |
400 |
claude-opus-4-6 |
accepted |
claude-sonnet-4-6 |
accepted |
claude-haiku-4-5-* |
accepted |
claude-3-* |
accepted |
OpenAI's reasoning models behave similarly but fail differently: the o-series and gpt-5* models take max_completion_tokens instead of max_tokens, use reasoning_effort in place of temperature, and expect the developer role rather than system. Same category of change, different symptom.
The fix
Delete the parameter for affected models:
const body = { model, messages, max_tokens };
if (!rejectsTemperature(model)) {
body.temperature = temperature;
}
If you were using temperature for a reason, the replacements are:
- Determinism → lower the effort setting and tighten the prompt. (Note
temperature: 0never guaranteed identical output anyway.) - Creative variance → ask for it explicitly in the prompt: "vary your phrasing and structure across responses." For design work, have the model propose several distinct directions and pick one.
The trap: your version check probably misses the new models
Here's the part that cost us a production outage.
The natural way to express "Claude 4.7 and above" is a regex over the model ID:
/^claude-(opus|sonnet|haiku)-4-([7-9]|\d{2,})/
That correctly matches claude-opus-4-7 and claude-opus-4-8. It is also completely wrong as of the Claude 5 release, because the new IDs are:
claude-opus-5 ← no "-4-" segment
claude-sonnet-5 ← no "-4-" segment
claude-fable-5 ← no "-4-" segment, and "fable" isn't in the alternation
Every Claude 5 model fails that test, so temperature gets sent, so every request 400s. The regex doesn't error — it just quietly returns false for exactly the models it most needs to catch.
A version check that matches on structure (-4- followed by a minor) breaks the moment the naming scheme changes. And the naming scheme changed: claude-opus-4-8 was followed by claude-opus-5, dropping a segment.
A more durable form matches on major version, with the legacy 4.x range kept separately:
const rejectsTemperature = (model) =>
/^claude-[a-z]+-([5-9]|\d{2,})(?!\d)/.test(model) || // any family, major 5+
/^claude-(opus|sonnet|haiku)-4-([7-9]|\d{2,})/.test(model); // the 4.7+ range
Verified against every current ID: it catches Opus 5, Sonnet 5, Fable 5, Opus 4.8 and 4.7, while leaving Opus 4.6, Sonnet 4.6, Haiku 4.5 and the 3.x line untouched.
Better still, don't infer capability from a model ID at all. A lookup table you update deliberately when adding a model is uglier and correct. A regex is elegant and will break on the next naming change — and it breaks silently, in the direction of sending a parameter that 400s.
How to catch this before your users do
We shipped this bug because our integration tests only exercised cheap models. claude-haiku-4-5 accepts temperature, so every test passed while every Claude 5 request failed.
Two things worth having:
- Test at least one model from each behavioural family, not just the cheapest. The cheapest model is usually the oldest, which is exactly the one that won't exhibit new breaking changes.
- Assert on response content, not status codes. A 400 is loud, but the related failure modes — reasoning blocks, refusals, budget exhaustion — all return HTTP 200 with nothing useful in them.
If you're migrating to a Claude 5 model, the full checklist is: drop temperature/top_p/top_k, drop budget_tokens (also removed — use effort), remove any last-assistant-turn prefill (also a 400), and raise max_tokens, because it now covers reasoning tokens as well as the answer.
Check a model yourself
- Run a prompt against a Claude 5 model — no account, no API key
To see which parameters a given model accepts across providers, that's Promptster.
Model behaviour verified against the Anthropic API on 2026-08-02 and 2026-08-09. The regex trap described here was a real bug in our own code: the -4- structural match silently excluded every Claude 5 model, and it shipped because our test matrix contained no Claude 5 model to catch it. Provider behaviour changes — re-verify before relying on any table in a blog post, including this one.