Claude 400: temperature is deprecated, and not alone
temperature, top_p and top_k return a 400 on Claude 4.7 and later. The other fields providers now reject, and where to strip them before dispatch.
Your request worked on Friday and returns HTTP 400 on Monday. You changed the model string, nothing else. The body is the same one you have been sending for two years, and somewhere in it is a field that is now forbidden.
This is request validation, not capacity. A 429 means the provider would have served you and will later. A 400 means the provider read your JSON, found something it will not accept, and will give you the same answer forever. Retrying is pointless. So is switching accounts. The only fix is to change the body.
The parameters Claude now refuses
Anthropic's model-deprecations page carries an "API parameter deprecations" section. As of 19 October 2026 it has exactly one row, and that row covers the three fields almost every client sets:
temperature,top_p,top_k— Deprecated (Claude 4.7 and later models) — "Returns a 400 error when set to a non-default value on Claude 4.7 and later models and Claude Mythos Preview."
The same section notes that most SDKs keep the fields in their request types so old code still type-checks, but that the Python SDK from v1.0 onward removes temperature, top_p and top_k, so passing them raises a TypeError before a request is even made. The SDK's message_create_params on main no longer defines any of the three, which matches.
There is a forcing function on the same page. claude-sonnet-4-5-20250929 was deprecated on 30 September 2026 with a tentative retirement date of 30 November 2026, and the recommended replacement is claude-sonnet-5-5. Every Sonnet 4.5 caller is being pushed, on a published date, onto a model that rejects fields its predecessor accepted.
Sampling parameters are only the first of them. The Sonnet 5.5 migration guide and the thinking documentation list several more, each with its own error text:
| What you send | What comes back |
|---|---|
temperature, top_p or top_k at a non-default value | 400. "On Claude Sonnet 5.5, a non-default value returns a 400 error. Remove them." |
thinking: {"type": "disabled"} | 400 invalid_request_error, telling you to send {"type": "between_tools"} instead |
thinking: {"type": "enabled", "budget_tokens": N} | 400: "thinking.type.enabled" is not supported for this model. |
tool_choice of type any or tool | 400: tool_choice: type "tool" and "any" are not supported for this model. |
| A prefilled last assistant turn | 400: This model does not support assistant message prefill. |
The tool_choice one catches people who never wrote it. A framework asking for structured output by setting tool_choice: "required" in OpenAI shape translates to {"type": "any"} in Anthropic shape, and the 400 names a field the application never mentioned.
The rejection list is also not uniform across one generation. Anthropic's thinking page says Opus 5 and Haiku 5.5 accept thinking: {type: "disabled"} at effort high or below and return a 400 at xhigh or max, while Fable 5, Fable 5.1, Mythos 5, Mythos 5.1, Opus 5.5 and Mythos Preview reject it outright. Which is why that page also tells you to stop guessing: read capabilities.thinking.types.disabled.supported from the Models API before you send.
It is not one vendor
Anthropic is simply the loudest instance this autumn. The pattern is older and wider, and it points both ways.
The same Anthropic pricing page documents the mirror image: inference_geo is a field Claude 4.6 and later accept, and "Earlier models do not support the inference_geo parameter and always use standard pricing; requests that include the parameter on these models return a 400 error." A new field 400s on old models while old fields 400 on new ones, inside one vendor, at the same time.
Groq publishes a short list on its OpenAI-compatibility page: "The following fields are currently not supported and will result in a 400 error (yikes) if they are supplied" — logprobs, logit_bias, top_logprobs, and messages[].name. That last entry matters, because it is not a top-level parameter at all. It is a key inside a message object, which means validation reaches into your conversation history.
Mistral made the switch years ago and wrote it down. Its changelog entry for 11 January 2024 says the API used to ignore unsupported parameters and now strictly checks that every parameter is valid: "If you have unsupported parameters in your request, you will see the error message 'Extra inputs are not permitted'." Anything built against the permissive behaviour broke on a date.
OpenAI's current migration guidance says the same thing in the imperative: when reasoning effort is not none, remove temperature, top_p and top_logprobs, and on Chat Completions also remove logprobs. It adds that GPT-6 Astra and GPT-6.1 Sol do not support none at all, so for those two the removal is unconditional.
The same field, rejected here and required there
The hardest version of this is not a field that one provider refuses. It is a field that one provider refuses and another insists on.
Clients driving reasoning models often echo the previous turn's chain of thought back on every assistant message, as reasoning_content, reasoning or reasoning_details. DeepSeek and Kimi established that convention and validate against it. Strict OpenAI-compatible validators do the opposite: an unknown key on a message object is a rejection, exactly as Mistral's changelog describes. So the identical multi-turn history is correct for one provider and invalid for another, and the failure only appears from the second turn onward, which is why it reads as flakiness rather than a schema error.
kRouter carries both halves. One table removes those three fields from assistant turns for Groq, Mistral and Cerebras. A second, separate table injects a placeholder reasoning_content on assistant turns for DeepSeek, MiniMax and OpenCode, and on tool-call turns for Kimi, because a client in OpenAI format never sends it and the response pipeline strips it before it reaches the client, so the echo can never come back on its own.
What does not work
Setting the parameter to its default. Anthropic's wording is "when set to a non-default value", which sounds like temperature: 1.0 is safe. It is not a strategy: the Python SDK removed the keyword entirely, so there is no value you can pass there, and on strict validators the key's presence is the violation, not its value.
Catching the 400 and retrying. A 400 is deterministic. The same body produces the same answer on every attempt, on every account, forever.
Failing over to another account. This is the expensive mistake. A router that treats any 4xx as an unhealthy credential will walk your entire account pool on a malformed body, cool every account down, and leave you with nothing available for a problem that was never about credentials. kRouter classifies an unmatched 4xx as request-scoped as of 0.5.166: no cooldown, no account failover, and the error goes straight back to you. A combo may still try its next entry, because a different model can legitimately accept what this one rejected. A short list of 400s that genuinely are about the credential — an empty credit balance, an invalid API key, an unsupported location — is named explicitly so those keep failing over.
Fixing it once in the client. You have one client and several providers behind it. The client cannot know which provider the request will land on, let alone which model within it.
Where the table belongs
If the rejection depends on the provider and the model, the strip belongs at the last hop that knows both. In kRouter that is one rule table consulted on dispatch, rather than delete body.x scattered through every executor.
A rule names an optional provider, an optional model regex or predicate, the body keys to drop and the assistant-message fields to drop. As of v0.5.166 there are four. Any model whose id matches claude, on any path, loses temperature — a blunt trade of fine sampling control for not hard-400ing. Copilot's gpt-5.4 family loses temperature. Copilot's Claude models other than opus 4.6 and sonnet 4.6 lose thinking and reasoning_effort, because only those two honour the latter there. Groq, Mistral and Cerebras lose the replayed reasoning fields from assistant turns.
A separate rewrite handles the shapes rather than the keys, keyed on a per-model capability flag for whether thinking can be turned off. On Sonnet 5.5 a thinking: {"type": "disabled"} becomes {"type": "between_tools"}, with effort clamped down to high because between_tools is rejected above that. On Opus 5.5 and Fable the switch is dropped. A forced tool_choice of any or tool becomes auto, keeping disable_parallel_tool_use if it was set. That runs on the translated path and again in the executor, because a Claude Code request passing straight through to a Claude provider never reaches the translator at all.
What kRouter does not do
It does not strip top_p or top_k. Anthropic 400s on all three sampling parameters and kRouter removes one of them, so a client that sets top_p will still fail on Claude 4.7 and later. Remove it in the client.
Its capability data does not mark the Mythos models as unable to turn thinking off, although Anthropic's thinking page lists Mythos 5, Mythos 5.1 and Mythos Preview among the models that reject disabled. Sending that body through kRouter to a Mythos model passes it on unchanged, and Anthropic answers 400.
The Codex executor still carries its own hand-written delete list rather than a row in the table. It works, but it is the pattern the table was meant to replace, so it is one more place to look when a Codex request loses a field you expected to survive.
And none of this is a substitute for reading the provider's own documentation. The table encodes failures that have been hit; it does not predict the next one.
Fixing it in your own code
- Read the error body, not the status. Anthropic names the field and usually the replacement. Mistral's "Extra inputs are not permitted" names the offending key. Groq's message names the unsupported field.
- Delete the field rather than defaulting it. On strict validators the key's presence is the violation.
- Ask the provider instead of hardcoding. Anthropic exposes
capabilities.thinking.types.disabled.supportedon the Models API for exactly this. Where a capability endpoint exists, read it. - Keep the exceptions in one place. One table of provider, model and fields beats branches in every call site, because the next provider quirk should be a row, not a code path.
- Do not let a 400 touch your retry or failover logic. Separate request errors from credential errors before anything decides to cool an account down.
If kRouter is where you want that hop, start it and point your client at the endpoint. Under Docker, generate a dashboard password first, because the default one only works from the machine kRouter runs on and inside Docker even your own browser counts as remote:
KROUTER_PASSWORD="$(openssl rand -base64 18)" && echo "Dashboard password: $KROUTER_PASSWORD"
docker run -d -p 20128:20128 \
-e INITIAL_PASSWORD="${KROUTER_PASSWORD:?run the line above first}" \
-v "$HOME/.krouter:/app/data" --name krouter sifxprime/krouter:latestWhere to put the strip depends on how many providers you have:
| Your situation | Where the strip belongs |
|---|---|
| One provider, one model | The client. Delete the field and move on. |
| One provider, several models | The client, behind a model check, or the provider's own capability endpoint. |
| Several providers, one client | A gateway. The client cannot know where the request lands. |
| Several providers and a fallback chain | A gateway, because the second attempt goes somewhere the first did not. |
Common questions
Is temperature: 1.0 safe on Claude 4.7 and later?
The documented wording is that a non-default value returns a 400, which implies the default does not. It is still the wrong thing to rely on: the Python SDK from v1.0 removes the keyword, so there is no value to pass, and the guidance on the deprecations page is to omit the parameter and steer behaviour through prompting instead.
Why did my request start failing when I only changed the model name?
Because the validation is per model, not per API. The endpoint, the SDK and the body are identical; the model id moved the request into a stricter validator. This is also why it survives a provider switch: route the same body to a Claude model through a different gateway and it fails the same way.
How do I tell a 400 from a 429 in practice?
By what changes on a retry. A 429 resolves with time or another account; a 400 never does. If a router retries a 400 across accounts you will see the same error repeated once per credential, which is the signature of failover logic treating a request error as a credential error. kRouter stopped doing that in 0.5.166.
Does a gateway hide breakage I should know about?
Yes, deliberately, and that is a real cost. When kRouter drops temperature for a Claude model your request succeeds, but the sampling control you asked for is gone and nothing in the response says so. If determinism matters to your output, check whether the field you set actually survives to the provider rather than assuming a 200 means it did.
Related
Published by Kodelyth, the team that builds kRouter. Posts are drafted with AI assistance and reviewed by a person before they go out. kRouter is free and MIT licensed.
Install kRouterRelated posts
- Fix an error429 is not one error: what kRouter does with eachA 429 can be a per-minute limit, a quota with a reset time, a daily cap or a monthly spend cap. How kRouter tells them apart and how long it waits.
- Fix an errorClaude Code 529 Overloaded: who sent it and how to fail overA 529 is not your quota: the model is out of capacity, and Claude Code has already retried. How to tell who sent it, and how to keep working.
- Fix an errorClaude Code context window with non-Claude models: the fixClaude Code assumes 200K for a model it doesn't recognize, so it compacts too early or overruns the real limit. How to declare the real window.
Relevant docs
- TroubleshootingFixes for common kRouter problems: banned or rate-limited accounts, OAuth sign-ins, MITM certificates, localhost and Cursor, Docker logins and the CLI.
- Error referenceWhat the common AI provider errors mean, why they happen, and how to get past them.
- Combos & fallbackPut several models behind one name. kRouter falls back from one to the next, rotates them, or asks a panel of models and merges the answers.