← Back to Blog

Fix: "Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead."

2026-09-17·4 min read·ClawRouters Team
max_tokens is not supported with this modelmax_completion_tokensunsupported_parameter max_tokensopenai max_tokens erroro1 max_completion_tokensopenai 400 unsupported parameter

TL;DR — Newer OpenAI models reject max_tokens and want max_completion_tokens. It is a rename, not a behaviour change you can ignore: the new name exists because reasoning models spend tokens thinking before they emit a single visible character, and the cap now covers both. Swap the parameter for those models — but if you call more than one model family, do not sprinkle if model.startswith(...) through your code; normalise the parameter in one place.

The error

HTTP 400
{
  "message": "Unsupported parameter: 'max_tokens' is not supported with this model. Use 'max_completion_tokens' instead.",
  "type": "invalid_request_error",
  "param": "max_tokens",
  "code": "unsupported_parameter"
}

It shows up the moment you point existing code at a newer model, which is what makes it feel like a regression: nothing in your code changed, only the model string did.

Why the parameter was renamed

Reasoning models generate internal reasoning tokens before the visible answer. Those tokens are billed and they occupy the completion budget, but you never see them. max_tokens used to mean "the length of the reply"; on a reasoning model that framing is wrong, because a large share of the budget can be consumed before the reply starts.

max_completion_tokens names the thing accurately: a cap on all tokens generated — reasoning plus visible output.

The practical consequence catches people out:

A limit that was generous for a chat model can be entirely consumed by reasoning, returning an empty or truncated answer with a length finish reason and a bill attached.

If you migrate by renaming the parameter and keeping the same number, budget too tight is the failure you should expect next.

The fix

One model family — rename it:

- max_tokens: 1024
+ max_completion_tokens: 4096   # leave headroom for reasoning tokens

A mixed fleet — normalise once, at the edge:

REASONING_STYLE = {"o1", "o3", "o4"}  # keep this list in config, not in code

def build_payload(model: str, budget: int, **kw) -> dict:
    key = ("max_completion_tokens"
           if any(model.startswith(p) for p in REASONING_STYLE)
           else "max_tokens")
    return {"model": model, key: budget, **kw}

The point is not the exact predicate — model naming keeps moving, and any hardcoded list goes stale. The point is that there is exactly one place to edit when it does. Scattering the branch across every call site is how a one-line rename turns into a week of whack-a-mole.

Related parameter rejections on the same models

The same family rejects several other long-standing parameters, and the error shape is identical (unsupported_parameter / unsupported_value):

Parameter What happens
temperature, top_p Rejected or restricted to the default — sampling is not yours to tune on these models
frequency_penalty, presence_penalty Commonly unsupported
max_tokens Rejected — use max_completion_tokens

So a migration usually surfaces as a sequence of 400s rather than one. Strip the unsupported parameters rather than discovering them one deploy at a time.

Prevention

Sourced from OpenAI's own error response; the exact string is quoted above so you can match on it.

Ready to Reduce Your AI API Costs?

ClawRouters routes every API call to the optimal model — automatically. Start saving today.

Get Started Free →

Get weekly AI cost optimization tips

Join 2,000+ developers saving on LLM costs