Claude's Max Output Tokens per Model
8 min read · updated August 11, 2026
Three different numbers get called “max output tokens”: the ceiling the model documents, the max_tokens you put in the request, and the largest response you can actually receive without the connection dropping. They interact, and only the first one is a property of the model.
The documented ceilings
Anthropic documents an output ceiling per model, published on the model overview page and returned as the max_tokens field on a Models API model object. At the time of writing, the current Opus and Sonnet generations document 128,000 output tokens, and Claude Haiku 4.5 documents 64,000. Older families documented much lower figures — the Claude 3 generation was in the 4,000 to 8,000 range — which is why code written against them often carries a max_tokens that is now an order of magnitude below what the model will do.
max_tokens from GET /v1/models/{id} — it is the same value the docs table is generated from, and it cannot drift.max_tokens is required, and it is a cap
Unlike several other providers, the Messages API has no default. Omit max_tokens and the request is rejected with a 400 before anything is generated. This is deliberate: the parameter is a hard spending limit, and a hard spending limit with a silent default is a bill nobody agreed to.
Two things follow. First, max_tokens is enforced by the server rather than suggested to the model — generation is cut when the count is reached, mid-sentence if necessary, and the response comes back with stop_reason: "max_tokens". The model is not told the number and cannot pace itself against it. Second, asking for more than the model’s ceiling is an error, not a clamp: a request with max_tokens above the documented figure returns a 400 naming the limit rather than quietly serving the maximum.
{
"model": "claude-opus-4-6",
"max_tokens": 200000,
"messages": [{"role": "user", "content": "Write the report."}]
}
// 400 invalid_request_error
// max_tokens: <value> > <model ceiling>, which is the maximum allowed
// number of output tokens for <model>Why the big ceilings need streaming
The ceiling is a model property; what you can receive is a transport property. Generation is sequential, so a 100,000-token response takes 100,000 sequential decode steps, and on a non-streamed request nothing crosses the wire until all of them have finished. Long before the model runs out of tokens, an idle HTTP connection somewhere between you and the API gets closed.
This is why the official SDKs refuse non-streaming requests whose max_tokens they estimate will exceed roughly ten minutes of generation, raising a client-side error rather than letting you discover the timeout in production. The fix is not a longer timeout; it is to stream. A streamed response delivers events continuously, so the connection is never idle and the practical limit becomes the model’s documented ceiling again.
- Under roughly 16,000 output tokens, a buffered request is fine.
- Above that, stream and reassemble — every SDK exposes a helper that hands you the completed message at the end, so streaming does not force you to handle events yourself.
- The ceiling itself does not change with streaming. Only your ability to receive what it allows does.
Thinking tokens come out of the same budget
On a model with extended or adaptive thinking enabled, the reasoning the model does before answering is output, billed as output and counted against max_tokens. A request with a 4,000-token cap on a thinking model can spend most of that budget reasoning and get cut off partway through the visible answer, which reads like the model losing the thread when it is simply the cap arriving early.
The practical rule is to size max_tokens for reasoning plus answer, not for answer alone, and to raise it substantially when turning thinking on. See the thinking budget parameter for how the two budgets relate.
Detecting truncation
Check stop_reason on every response before using the content. The value end_turn means the model finished; max_tokens means it did not. Treating a truncated response as complete is the failure that produces malformed JSON, half-written files and tool calls with missing arguments — and it is invisible if you only read content[0].text.
const msg = await client.messages.create({ model, max_tokens: 8192, messages });
if (msg.stop_reason === "max_tokens") {
// The answer is incomplete. Retry with a higher cap, or continue the
// turn by sending the partial content back as history.
}On a streamed request the same information arrives on the message_delta event near the end of the stream, in delta.stop_reason, alongside the final output token count. This is the asymmetry that catches streaming integrations: in a buffered response you can check stop_reason before rendering anything, and in a streamed one you cannot, because the text was delivered first and the verdict arrives last.
The severity of a truncation depends entirely on what was being generated, and the ranking is worth having in mind when you choose a cap:
- Prose degrades gracefully. A cut-off paragraph is visibly incomplete. The user can see the problem and ask for the rest.
- Code degrades badly. A file that stops mid-function is syntactically invalid, and if you write it to disk without checking
stop_reasonyou have corrupted a file rather than failed to write one. - Structured output degrades silently and totally. Truncated JSON is not partially usable — it is unparseable, and the exception you get is a JSON syntax error many layers away from the cap that caused it. The same applies to a truncated tool call: the accumulated
input_json_deltafragments never form a valid object, so the tool cannot be dispatched at all.
Continuing after a truncation
The obvious repair is to send a follow-up saying “continue”. It works better than it has any right to, and it has two specific failure modes worth knowing before you rely on it.
The mechanism is that you append the truncated assistant turn to the conversation as history and add a user turn asking for the remainder. The model then sees its own partial output and carries on from it:
messages: [
{ role: "user", content: "Write the migration runbook." },
{ role: "assistant", content: truncated.content }, // verbatim, incomplete
{ role: "user", content: "Continue from exactly where you stopped." }
]The first problem is the seam. The model resumes at a plausible boundary rather than at the exact character it stopped on, so concatenating the two responses often produces a repeated clause or a missing one. For prose this is cosmetic; for code it means a duplicated line in the middle of a function, which compiles about half the time and is worse when it does.
The second problem is that it does not work at all for structured output. A truncated JSON object cannot be continued by a model that is being asked to produce a fresh, complete object, and a truncated tool call cannot be resumed because the partial arguments were never a valid input. For those, the only repair is to reissue with a higher cap or a smaller task — which is the real lesson: continuation is a recovery for long prose, and for anything with a grammar the fix belongs upstream, in sizing the request so truncation does not happen.
The upstream version is usually decomposition. A request that reliably runs into the ceiling is a request doing too much in one turn, and splitting it into sections the model emits one at a time costs an extra round trip and removes the failure entirely.