Set Max Tokens

Su Suriya Ravichandran Updated 10 Nov 2026
12 min read ·Lesson 13 of 200

What max_tokens Is

max_tokens is a required parameter of every Messages API request. It sets the maximum number of tokens Claude may generate in its response:

message = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Explain recursion."}],
)

It is an upper limit, not a target. Claude does not try to fill it. When the answer is complete, Claude stops on its own, often far below the limit. max_tokens only matters when the response would otherwise be longer.

What a Token Is

A token is the unit of text a model reads and writes: a word, part of a word, a punctuation mark, or a piece of whitespace. Common English words are often one token; longer or rarer words are split into several.

As a rough guide, the tokenizer used by current Claude models fits about 555,000 English words into 1 million tokens, so one word averages a little under two tokens. Code, numbers, and non-English text usually use more tokens per word. Exact counts are available from the API (Lessons 44–46).


Why max_tokens Exists

The limit protects you in several ways:

PurposeExplanation
Cost controlYou pay for output tokens. The limit caps the maximum cost of any single response.
Latency controlGenerating tokens takes time. A limit caps how long a response can take.
Resource protectionIt prevents a runaway response, for example one that repeats itself, from producing huge outputs.
PredictabilityYour application knows the largest response it might have to store, display, or process.

Why it is required rather than optional: the API makes you decide explicitly how much output you are willing to accept, instead of applying a hidden default that might not suit your application.


How max_tokens Affects the Response

Two outcomes are possible:

1. Claude finishes before the limit. The response is complete, and stop_reason is "end_turn":

message = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=2048,
    messages=[{"role": "user", "content": "What is 2 + 2? Answer with just the number."}],
)

print(message.stop_reason)          # end_turn
print(message.usage.output_tokens)  # A small number, far below 2048

2. Claude reaches the limit. Generation stops immediately, possibly mid-sentence, and stop_reason is "max_tokens":

message = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=300,
    messages=[{"role": "user", "content": "Write a detailed guide to Python packaging."}],
)

print(message.stop_reason)          # max_tokens

The response is truncated. It is not an error and no exception is raised, so your code must check stop_reason to notice.

You Pay for Tokens Generated, Not for the Limit

Billing uses the actual number of output tokens, reported in message.usage.output_tokens. A request with max_tokens=8000 that produces a 150-token answer is billed for 150 output tokens, plus any thinking tokens.

Consequence: a generous max_tokens does not increase cost for short answers. It only allows long answers when they are needed.


Thinking Tokens Count Toward max_tokens

This is the most important detail for current models.

Claude Opus 5.5 and Claude Fable 5.1 always use adaptive thinking, and the other current models use it by default: the model may reason internally before writing its answer. Thinking tokens count toward max_tokens, and they are billed as output tokens even though the thinking text is not shown by default.

max_tokens = 1,000
┌──────────────────────────────┬──────────────────────┐
│ thinking: 700 tokens         │ answer: 300 tokens   │
└──────────────────────────────┴──────────────────────┘
          both count toward the same 1,000-token limit

This has a surprising consequence. With a small max_tokens, the model can spend the whole budget thinking and stop before writing any text:

message = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=30,
    messages=[{"role": "user", "content": "Compare three sorting algorithms."}],
)

print(message.stop_reason)                         # max_tokens
print([block.type for block in message.content])   # Possibly ['thinking'] with no text

The response contains a thinking block and no answer at all.

How to Avoid It

  1. Leave headroom. Set max_tokens high enough for both thinking and the answer.
  2. Lower the effort for simple tasks. The effort setting tells the model how much to think. Less thinking leaves more of the budget for the answer, and reduces latency and cost:
message = client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    output_config={"effort": "low"},
    messages=[{"role": "user", "content": "Classify as positive or negative: 'Great service!'"}],
)

Claude Opus 5.5's default effort is medium. Effort is covered in depth with the advanced thinking features (Lessons 151–153).


Valid Range

ConstraintValue
Minimum1
Maximum for current models (Messages API)128,000 (128K) tokens
Maximum with the Message Batches APIUp to 300K on supported models, with the output-300k-2026-03-24 beta header

A value above the model's maximum is rejected with a 400 error. The maximum differs between models, especially older ones, so read it from the Models API instead of hardcoding it:

model_info = client.models.retrieve("claude-opus-5-5")
print(model_info.max_tokens)   # 128000

Relationship with the Context Window

The context window (1M tokens for current models) must hold both the input and the generated output. If your input is very large, the model may run out of context space before reaching max_tokens. In that case, the response ends with:

stop_reason == "model_context_window_exceeded"

As with max_tokens, the response is valid but truncated. This situation is rare unless you send extremely large inputs, such as long document collections or very long conversations.

Very Large Values Need Streaming

Generating tens of thousands of tokens can take minutes. Holding a single HTTP connection open and silent for that long is unreliable, because some networks drop idle connections. For that reason, the SDK raises a ValueError for non-streaming requests that it expects to take longer than about 10 minutes, based on the max_tokens you request.

Two ways to handle long outputs:

  • Use streaming (Lessons 51–60). This is the recommended approach. The response arrives piece by piece, and the connection stays active.
  • Set a timeout explicitly on the client or request, which also disables the check (Lesson 10).

Note: For long responses, streaming also gives users a much better experience. They see text appear immediately instead of waiting for the whole response.


Choosing a Value

There is no single correct number. Choose based on the longest complete answer you expect, plus room for thinking:

TaskTypical answer lengthSuggested max_tokens
Classification, yes/no, a labelA few tokens1,024 (headroom for thinking), with low effort
Short answers, one or two paragraphs50–300 tokens1,024–2,048
Explanations, emails, summaries300–1,500 tokens4,096
Long articles, detailed reports, larger code files2,000–8,000 tokens8,192–16,384, with streaming
Very long generation, such as complete documents or big refactors10,000+ tokens32,000+, with streaming

Why the small tasks still get 1,024: with thinking models, a tiny limit risks a response with no text. The generous limit costs nothing extra, because you pay only for tokens actually generated.

max_tokens Is Not a Length Instruction

max_tokens cuts off a response. It does not make Claude write shorter. If you want a short answer, ask for one in the prompt:

# Wrong way to get a short answer: produces a truncated long answer
client.messages.create(model="claude-opus-5-5", max_tokens=60, messages=[...])

# Right way: request the length, and keep a safe limit
client.messages.create(
    model="claude-opus-5-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": "In exactly two sentences, explain what an API is."}],
)

Claude plans its answer around what you ask for. It does not plan around max_tokens, so a low limit simply interrupts it. Lesson 38 (Control Response Length) covers prompting for length in detail.

GoalTool
A shorter or longer answerInstructions in the prompt
A safety ceiling on cost and timemax_tokens
Less thinking for simple taskseffort

Detecting and Handling Truncation

Every application that uses the answer should check stop_reason:

from anthropic.types import Message


def get_text(message: Message) -> str:
    return "".join(block.text for block in message.content if block.type == "text")


def is_truncated(message: Message) -> bool:
    return message.stop_reason in {"max_tokens", "model_context_window_exceeded"}

When a response is truncated, there are three sensible options:

OptionWhen to use
Retry with a higher limitThe answer should fit within the model's maximum
Stream with a higher limitLong outputs, where streaming avoids timeouts
Ask for a shorter answer, or split the task into partsThe task genuinely produces too much output

Never show or store a truncated answer as if it were complete. For example, truncated JSON will fail to parse, and a truncated summary may omit important conclusions.


Practical Example: Automatic Retry on Truncation

This function requests an answer with a reasonable limit. If the response is cut off, it retries once with a larger limit, capped at the model's maximum, and reports how many output tokens were used:

"""complete_answer.py: request an answer and retry once if it is truncated."""

from anthropic import Anthropic
from anthropic.types import Message

MODEL = "claude-opus-5-5"
client = Anthropic()

MODEL_MAX_TOKENS = client.models.retrieve(MODEL).max_tokens


def get_text(message: Message) -> str:
    return "".join(block.text for block in message.content if block.type == "text")


def create(prompt: str, max_tokens: int) -> Message:
    return client.messages.create(
        model=MODEL,
        max_tokens=max_tokens,
        messages=[{"role": "user", "content": prompt}],
    )


def ask_complete(prompt: str, max_tokens: int = 4096) -> str:
    """Return a complete answer, retrying once with a larger limit if needed."""
    message = create(prompt, max_tokens)

    if message.stop_reason == "max_tokens":
        larger = min(max_tokens * 4, MODEL_MAX_TOKENS)
        if larger > max_tokens:
            print(f"[info] Truncated at {max_tokens} tokens; retrying with {larger}.")
            max_tokens = larger
            message = create(prompt, max_tokens)

    if message.stop_reason in {"max_tokens", "model_context_window_exceeded"}:
        raise RuntimeError(
            f"Answer still incomplete (stop_reason={message.stop_reason}). "
            "Ask for a shorter answer or split the task."
        )

    print(f"[info] Used {message.usage.output_tokens} of {max_tokens} allowed output tokens.")
    return get_text(message)


if __name__ == "__main__":
    answer = ask_complete(
        "Write a step-by-step guide to setting up a Python project with a "
        "virtual environment, dependencies, and tests.",
        max_tokens=1024,
    )
    print(answer)

Example output when the first attempt is too small:

[info] Truncated at 1024 tokens; retrying with 4096.
[info] Used 2310 of 4096 allowed output tokens.
## 1. Create the project folder
...

How It Works

  1. MODEL_MAX_TOKENS is read once from the Models API at startup, so the retry never exceeds what the model allows. That works for any model, without hardcoding 128,000.
  2. create() wraps the request so that both attempts send exactly the same prompt and differ only in the limit.
  3. The first request uses a moderate limit, which keeps typical requests fast.
  4. If stop_reason is "max_tokens", the function retries with four times the limit, capped by min(...) at the model maximum. The if larger > max_tokens check avoids a pointless retry when the limit was already at the maximum.
  5. The final check raises an error if the answer is still incomplete, instead of returning partial text that callers might mistake for a full answer.
  6. The usage line shows how much of the allowed budget was used, including thinking. Logging this over time shows whether your default limit is well chosen.
  7. The retry costs extra. The first attempt's tokens are billed too. For outputs you know will be long, start with a higher limit, and use streaming, instead of relying on retries.

Note: For very large limits, switch this function to streaming (Lessons 51–60). Otherwise the SDK's long-request check may raise a ValueError before the request is sent.


Monitoring Output Tokens

Every response reports what was actually used:

print(message.usage.input_tokens)    # Tokens you sent
print(message.usage.output_tokens)   # Tokens generated, including thinking

Logging these values over time tells you:

  • whether your max_tokens values are realistic: how often you hit the limit, and how close typical responses come,
  • how much thinking a task uses at a given effort level, and
  • what each feature of your application costs.

Common Mistakes

Using max_tokens to Make Answers Shorter

What happens: answers end mid-sentence instead of being concise.

Fix: ask for the length you want in the prompt, and keep max_tokens as a safety ceiling.

Setting max_tokens Too Low for a Thinking Model

What happens: responses intermittently contain no text, with stop_reason == "max_tokens", because thinking consumed the budget.

Fix: leave generous headroom (1,024 or more even for tiny answers), and lower effort for simple tasks.

Not Checking stop_reason

What happens: truncated answers are displayed, saved, or parsed as if complete. Truncated JSON raises parsing errors that look unrelated to token limits.

Fix: check for max_tokens and model_context_window_exceeded on every response your application depends on.

Exceeding the Model's Maximum

What happens: max_tokens=200000 returns a 400 error for models whose maximum is 128,000.

Fix: read the maximum from the Models API, or keep values within the documented limit.

Requesting Huge Outputs Without Streaming

What happens: the SDK raises ValueError for requests it expects to take more than about 10 minutes, or long requests time out on unreliable networks.

Fix: use streaming for large max_tokens values.

Estimating Cost from Visible Text

What happens: costs are higher than expected, because thinking tokens are billed as output but not shown.

Fix: calculate cost from usage.output_tokens.


Best Practices

  • Treat max_tokens as a ceiling, not a length control. Request length in the prompt.
  • Leave room for thinking, especially on Claude Opus 5.5 and Fable 5.1, where thinking is always on.
  • Use effort to reduce thinking on simple, high-volume tasks.
  • Always check stop_reason before using a response.
  • Use streaming for long outputs, typically when max_tokens is above about 8,000–16,000.
  • Read model limits from the Models API when your code works with several models.
  • Log usage.output_tokens and tune limits from real data.
  • Make limits configurable (Lesson 8), so they can be adjusted without code changes.

Exercise

  1. Ask Claude to "Write a detailed history of the Python language" with max_tokens=200. Print stop_reason, the block types, and usage.output_tokens.
  2. Repeat with max_tokens=4096. Compare stop_reason and the length of the answer.
  3. Ask a one-word classification question with max_tokens=1024, first with the default effort and then with output_config={"effort": "low"}. Compare usage.output_tokens.
  4. Change ask_complete() so it returns a tuple of (text, output_tokens) instead of printing the usage line.
Show answers
  1. stop_reason is max_tokens. The block list may be ['thinking', 'text'] with a cut-off text block, or only ['thinking'] if the budget ran out during thinking. output_tokens is 200 or very close to it.
  2. stop_reason is usually end_turn. The answer is complete, and output_tokens is whatever was needed, typically well below 4096.
  3. With low effort, output_tokens is usually smaller, because the model spends fewer tokens thinking. The exact numbers vary from run to run.
  4. A version that returns the usage:
def ask_complete(prompt: str, max_tokens: int = 4096) -> tuple[str, int]:
    """Return (answer, output_tokens), retrying once with a larger limit if needed."""
    message = create(prompt, max_tokens)

    if message.stop_reason == "max_tokens":
        larger = min(max_tokens * 4, MODEL_MAX_TOKENS)
        if larger > max_tokens:
            message = create(prompt, larger)

    if message.stop_reason in {"max_tokens", "model_context_window_exceeded"}:
        raise RuntimeError(f"Answer still incomplete (stop_reason={message.stop_reason}).")

    return get_text(message), message.usage.output_tokens


text, used = ask_complete("Explain Python generators with an example.", max_tokens=1024)
print(text)
print(f"Output tokens used: {used}")

Returning values instead of printing them lets the caller decide what to do with the usage, such as logging it, storing it, or showing it to an administrator.

0 Comments

Reviewed before they appear

No comments yet.

Anthropic SDK Using Python
Anthropic SDK Intro Claude API Intro SDK Requirements Install Anthropic SDK Upgrade the SDK Create an API Key Set API Keys Use Environment Variables Import Anthropic Create a Client Send Your First Message Choose a Claude Model Set Max Tokens Read a Response Print Response Text Understand Message Blocks Use System Prompts Send User Messages Set Temperature Handle Stop Reasons Messages API Intro Create Messages Use Message Roles Send Conversation History Continue a Conversation Build a Chat Loop Reset Chat History Use Multiple Turns Inspect Message IDs Read Usage Data Write Clear Prompts Set a System Prompt Add Context to Prompts Use Prompt Templates Add Examples to Prompts Request JSON Output Use XML Tags Control Response Length Improve Prompt Quality Avoid Prompt Injection Read Text Blocks Read Content Blocks Check Stop Reasons Count Input Tokens Count Output Tokens Count Tokens Before Sending Limit Output Tokens Use Stop Sequences Read Request IDs Parse API Responses Streaming Intro Stream Text Output Use Stream Events Use Stream Helpers Get Final Messages Stream Async Responses Handle Stream Errors Build a Live Chat Stream Long Responses Close Streams Safely Build a Chatbot Keep Chat History Manage Chat Sessions Add Chat Memory Build a CLI Chatbot Build a Web Chatbot Use Flask with Claude Use FastAPI with Claude Return JSON from an API Add Chat Error Messages Async SDK Intro Create an Async Client Send Async Messages Use asyncio Run Parallel Requests Stream Async Text Handle Async Errors Set Async Timeouts Close Async Clients Choose Sync or Async Tool Use Intro Define a Tool Create Tool Schemas Send Tools to Claude Read Tool Calls Run Python Tools Return Tool Results Continue After Tool Use Control Tool Choice Handle Tool Errors Use the beta_tool Decorator Create a Tool Runner Add Tool Docstrings Validate Tool Inputs Use Multiple Tools Limit Tool Calls Log Tool Activity Build a Weather Tool Build a Search Tool Build a Calculator Tool Structured Output Intro Request JSON Data Validate JSON Output Use Pydantic Models Handle Invalid JSON Extract Fields from Text Generate Typed Data Use Image URLs Send Base64 Images Read PDFs with Claude Use Output Schemas Build Data Extraction Test Structured Responses Send Image Inputs Upload Files Use File IDs Process Text Files Summarize Documents Extract Table Data Handle File Errors Read Cache Usage Reduce Repeated Input Costs Debug Cache Misses Prompt Caching Intro Enable Prompt Caching Cache System Prompts Cache Long Context Test Cache Performance Use Cache Breakpoints Cache Best Practices Message Batches Intro Create a Message Batch Set Batch IDs Check Batch Status List Message Batches Get Batch Results Process Batch Outputs Handle Failed Batch Items Use Batches for Bulk Tasks Compare Batches and Requests Handle API Errors Handle Rate Limits Retry Failed Requests Set Request Timeouts Handle Network Errors Use Exponential Backoff Build Reliable API Calls Use Extended Thinking Log API Errors Track Request IDs Paginate API Results Set Thinking Budgets Read Thinking Blocks Use Tool Search Use Web Search Tools Use Computer Tools Use Citations Use Token Counting Read Raw Responses Set Custom Headers Use Claude on AWS Bedrock Use Claude on Vertex AI Use Claude on Microsoft Foundry Configure Cloud Credentials Choose a Cloud Provider Compare API Integrations Configure Provider Clients Handle Cloud Errors Use Cloud SDK Extras Deploy Cloud Apps Protect API Keys Use a .env File Ignore Secrets in Git Validate User Input Reduce Prompt Injection Limit Tool Permissions Avoid Unsafe Tool Calls Sanitize Tool Results Set Usage Limits Review Security Logs Test SDK Functions Mock API Responses Test Error Handling Test Streaming Code Measure Token Usage Track API Costs Configure Logging Deploy with Docker Deploy a FastAPI App Monitor Production Apps Build a Claude Chatbot Build a Text Summarizer Build a Code Explainer Build a JSON Extractor Build a PDF Assistant Build a Streaming Chat App Build a Tool-Calling Agent Build a Batch Processor Build a Research Assistant Build a Claude API Dashboard
Ask about this post
AI Ask about this post

Ask questions about Set Max Tokens and get answers drawn from it.

Signed-in readers only.