Fixing "temperature must be between 0 and 1" After Switching Providers
9 min read · updated August 11, 2026
The request body did not change. The model string did. And now every call comes back 400 with a complaint about a temperature that has been in the config file, unremarked, for a year.
The error
You will see it in one of a few shapes depending on whose validator rejected it, but all of them are an HTTP 400 with a body identifying the offending field:
{"type": "error",
"error": {"type": "invalid_request_error",
"message": "temperature: Input should be less than or equal to 1"}}{"error": {"message": "1.4 is greater than the maximum of 1 - 'temperature'",
"type": "invalid_request_error", "param": "temperature", "code": null}}Through an SDK it usually surfaces as a bad-request exception carrying that message, and through a framework it may be wrapped again — the Vercel AI SDK, for example, wraps provider errors in its own error type, so the useful string is on a nested cause rather than on the exception you caught. Whatever the wrapper, look for the field name and the bound: those two facts are the whole diagnosis.
The exact wording differs by provider and by validator version, and several providers use a schema-validation library whose messages read like the first example above rather than like prose. Do not match on the message text in code. Match on the status, the error type and the param field where one is provided.
Why the same value worked yesterday
Because the accepted range for temperature is a per-API decision, not a property of the concept. OpenAI’s Chat Completions API documents temperature over a 0-to-2 range; Anthropic’s Messages API documents 0 to 1; Google documents a range per model in the generation config. A configuration file holding 1.4 is valid on the first and rejected by the second, and nothing about the value itself announces which world it came from.
Three specific paths lead here, and it is worth knowing which one you are on because the fix differs.
- You changed provider and kept the config. The common case. The value is out of range on the new API and the fix is to choose a value for the new model.
- You changed model within one provider. Some models constrain the parameter more tightly than the API does, and reasoning models in particular may not accept it at all. The API reference for the endpoint will not tell you this; the model’s own documentation will.
- You put a gateway or proxy in front. An OpenAI-compatible layer validates against the OpenAI-shaped range and then forwards to a backend with a different one, so the rejection can come from either side and the message will look like it came from OpenAI either way. How OpenAI-compatible endpoints handle unsupported parameters covers the general shape of that confusion.
The other error that looks like this
A distinct failure is easy to mistake for a range problem, because it also names temperature and also returns 400:
{"error": {"message": "Unsupported value: 'temperature' does not support 0.2 with this model. Only the default (1) is supported.",
"type": "invalid_request_error", "param": "temperature", "code": "unsupported_value"}}This is not a bound being exceeded. It is a model that does not accept the parameter at anything other than its default, which is the case on several reasoning models across providers. Lowering the value does not help — 0.2 and 1.4 are equally rejected. The fix is to omit the parameter entirely for that model, which means your adapter needs to know the difference between “clamp to the range” and “do not send this at all”. If your capability table has one boolean for “supports temperature”, it cannot express this, and you will find out by clamping to the boundary and still getting a 400.
The fix, and where it belongs
The immediate fix is to send a value inside the accepted range, or to omit the parameter where the model rejects it. The thing worth arguing about is what value, and the answer people reach for first is the wrong one.
Do not halve it. Mapping 1.4 on a 0-to-2 scale onto 0.7 on a 0-to-1 scale is arithmetic, not translation. Temperature scales the logits of a specific model, and the same number produces different amounts of variation on different models regardless of what the accepted range is — the mapping page makes that argument in full. Rescaling produces a number that passes validation and an output distribution nobody chose.
Instead: start at the new model’s default, run your own evaluation set, and move from there. If the old high temperature existed for a reason — generating varied candidates, avoiding repetitive phrasing — check whether the new model needs it at all. If the old value was copied from an example three years ago, which is the more common history, this is the moment to stop carrying it.
On placement: the range check belongs in the adapter that talks to the provider, not in the caller and not in the config loader. That is the only layer that knows which model the request is going to, and it is the layer that must also decide between rejecting and omitting. Two rules make it behave:
- Keep the accepted range and the “parameter unsupported” flag per route in one capability table, alongside the other parity facts, rather than as inline conditionals at each call site.
- Reject an out-of-range temperature loudly rather than clamping it. A caller asking for 1.6 has an intent your adapter cannot honour, and a clamp to 1.0 satisfies the validator while quietly changing the behaviour. Return an error naming the parameter, the requested value and the accepted range.
- Where the model rejects the parameter outright, drop it and record the drop on your response object so a caller who set it can see that it was not applied.
- Add a test that sends the boundary value for each configured route. This is the cheapest possible regression, it runs in a second, and it catches a provider tightening a range before your users do.
The version of this with no error at all
The 400 is the good outcome. The bad one is a proxy or an OpenAI-compatible endpoint that clamps or ignores the value instead of rejecting it. You send 1.8, the endpoint returns 200, and the output is sampled at whatever the backend actually used — possibly the default, possibly a clamped bound. Nothing in the response says so, and the symptom is output that is less varied than you configured, which nobody attributes to a parameter that appeared to be accepted.
The probe is direct: send the same prompt several times at a temperature that should produce obvious variation, and see whether it does. If a value near the top of the range produces output as stable as a value near zero, the parameter is not reaching the sampler. That is a capability gap, not a configuration problem, and it belongs in the table with the rest of them.