◉CLAUDE LABJP
●2.1.310 — Monitor tool now works on Bedrock, Vertex and Foundry, and gateway failures are fixed (10/23)●2.1.307 — Fixes MCP failing to reconnect after sleep or a keychain lock (10/21). 2.1.306 also fixed the false claude -p login message●SONNET 4.5 — Retires on the Claude API on 11/30, 36 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●API — In API version 2026-10-21, passing temperature, top_p or top_k returns a 400 error●NEW — I Put a Ten-Line Promise File at the To…●2.1.310 — Monitor tool now works on Bedrock, Vertex and Foundry, and gateway failures are fixed (10/23)●2.1.307 — Fixes MCP failing to reconnect after sleep or a keychain lock (10/21). 2.1.306 also fixed the false claude -p login message●SONNET 4.5 — Retires on the Claude API on 11/30, 36 days left. Migrate to Sonnet 5.5●WINDOWS — Reports say hooks break silently only on Japanese-locale Windows; check the character encoding first●API — In API version 2026-10-21, passing temperature, top_p or top_k returns a 400 error●NEW — I Put a Ten-Line Promise File at the To…
Articles/API & SDK
⬡ API & SDK/2026-10-25Intermediate

Before upgrading the SDK turns temperature into a 400, I swept my call sites

With API version 2026-10-21, sending temperature, top_p, or top_k returns a 400 even at the default value. Here is how the switch happens through an SDK upgrade, a small scanner for the lines to fix first, and the order I'd fix them in.

Claude API133API versiontemperatureMigration8Script

On a Sunday night I was going through the small scripts my indie-developer backend uses to call the API, and temperature=0.2 turned up far more often than I expected.

I wrote every one of those lines. Yet for about half of them I couldn't remember which script wanted 0.2, or why.

What sent me looking was the official migration page for API version 2026-10-21. It says that sending temperature, top_p, or top_k returns a 400, even when the value equals the default.

What I'd like to say up front is that you hit this when you upgrade the SDK, not when you change the model. You can leave the model ID untouched and still be stopped by the next deploy after a dependency bump.

What changes (as far as the migration page confirms)

The page gives no cutover date and no deadline. It names the new version string, 2026-10-21, and the previous one, 2023-06-01. With no date, the question isn't "when will it break" but "when do I choose to switch."

There are two ways the switch happens. With an SDK, the API version is tied to the SDK's major version; for Python, upgrading anthropic to 2.0.0 is what starts sending 2026-10-21. Without an SDK, it happens when you change the anthropic-version header yourself.

Three things in the Messages API now return a 400.

What you sendBehavior in 2026-10-21Fix
temperature / top_p / top_k400 even at the default (token counting and batches too)Remove the parameter
A document block directly in an assistant message400Move it into a user message
A non-empty citations array on a user text block400Drop the citations; assistant-side citations and citations.enabled on documents still work

One more point on sampling. Claude 4.7 and later models already accept only the defaults, so calls to those behave the same and you just delete the argument. The place to be careful is a model that did accept other values (the page's example is Sonnet 4.6) where you had lowered temperature. Delete the argument and it runs at the default of 1.0, so nothing errors, but the spread of the output changes.

Drawing a line before searching

At first I assumed "delete them all" was the whole job, and the result wasn't great. Sorting which lines are safe to delete from which lines should stay on the old version took longer than the deleting itself.

This is the line I use now.

  • Calls to 4.7 or later: delete the argument (behavior doesn't change)
  • Calls to 4.6 or earlier that don't depend on output variance: delete it, then compare a few outputs
  • Calls to 4.6 or earlier that depend on temperature near 0: keep that one request on 2023-06-01, or pin the output shape in the prompt

The third follows the page, which says that if a request depends on a non-default value, you should stop sending the parameter or keep that request on 2023-06-01.

A scanner to find them

Having a machine list candidates and a person judge them worked better than eyeballing grep output. I turned the items on the migration page that are easy to spot in code into regular expressions. It isn't a parser, so comments and variable names will give false positives; I accepted that to miss fewer real ones.

#!/usr/bin/env python3
"""Rough scanner for code that may return a 400 or change behavior on API version 2026-10-21.
Usage: python3 scan_20261021.py <dir> [<dir> ...]   exits 1 if any candidate is found"""
import re, sys, pathlib
 
EXTS = {".py", ".ts", ".tsx", ".js", ".mjs", ".rb", ".go", ".java", ".json", ".yaml", ".yml", ".sh"}
SKIP = {"node_modules", ".git", ".venv", "dist", "build", ".next"}
 
RULES = [
    # (id, regex, what to do next)
    ("sampling",   r"""["']?\b(temperature|top_p|top_k)\b["']?\s*[:=]""", "remove it; if you rely on a non-default value, keep only that request on the old version"),
    ("version",    r"anthropic-version.{0,20}2023-06-01",                   "change the header to 2026-10-21 together with the fixes"),
    ("auth-hdr",   r"""x-api-key""",                                        "new SDKs send Authorization: Bearer; check what your proxy reads"),
    ("msgparam",   r"\bMessageParam\(",                                     "use a dict literal or a typed assignment"),
    ("compaction", r"\bcompactionControl\b",                                "replace with context_management compaction"),
    ("completions",r"\bcompletions\.create\b",                              "Text Completions was removed; use the Messages API"),
    ("assist-doc", r"""["']role["']\s*:\s*["']assistant["'].{0,200}["']type["']\s*:\s*["']document["']""", "move document blocks to a user message"),
]
 
def scan(root: pathlib.Path):
    hits = []
    for p in root.rglob("*"):
        if not p.is_file() or p.suffix not in EXTS or SKIP & set(p.parts):
            continue
        try:
            lines = p.read_text(encoding="utf-8", errors="ignore").splitlines()
        except OSError:
            continue
        for no, line in enumerate(lines, 1):
            for rid, rx, todo in RULES:
                if re.search(rx, line, re.I if rid == "auth-hdr" else 0):
                    hits.append((str(p), no, rid, line.strip()[:90], todo))
    return hits
 
if __name__ == "__main__":
    roots = [pathlib.Path(a) for a in sys.argv[1:]] or [pathlib.Path(".")]
    found = [h for r in roots for h in scan(r)]
    for path, no, rid, text, todo in found:
        print(f"{path}:{no} [{rid}] {text}\n    -> {todo}")
    print(f"\n{len(found)} candidates")
    sys.exit(1 if found else 0)

The assist-doc rule only catches a dict literal on a single line. Messages wrapped across several lines will slip through, so check anywhere you use document by eye as well.

The run on a tiny sample

I put three files in a folder: a Python file passing temperature=0.2, a TypeScript file with top_p, and a file that merely contains the word temperature. Then I ran the scanner.

t/app.py:6 [sampling] temperature=0.2,
    -> remove it; if you rely on a non-default value, keep only that request on the old version
t/app.py:9 [msgparam] m = anthropic.types.MessageParam(role="user", content="x")
    -> use a dict literal or a typed assignment
t/proxy.ts:1 [auth-hdr] const key = req.headers["x-api-key"];
    -> new SDKs send Authorization: Bearer; check what your proxy reads
t/proxy.ts:2 [sampling] const body = { model: "m", top_p: 0.9, messages: [] };
    -> remove it; if you rely on a non-default value, keep only that request on the old version
 
4 candidates

The file with only the word was not flagged, and the exit code was 1. Four hits, as expected.

The auth-hdr rule is the easy one to miss. New SDKs send the API key as Authorization: Bearer rather than X-Api-Key. If a proxy or gateway of your own validates the key by reading X-Api-Key, it may stop requests at authentication, before the sampling 400 is ever reached. If you run a thin relay in front of your backend, check that before you bump the SDK.

The order I fix things in

Fixing everything at once leaves you unsure what helped. I go in this order.

  1. Run the scanner and count
  2. Sort lines to keep from lines to delete, using the line above
  3. Delete the arguments while still on the old SDK and commit (the change is safe on both versions)
  4. Upgrade the SDK last; the migration page also says to release the upgrade together with the fixes, so I ship steps 3 and 4 the same day

Putting step 3 in first means that if a 400 shows up on upgrade day, you can rule out sampling arguments as the cause.

A few other SDK changes are worth jotting down. Calling MessageParam(...) as a class raises TypeError. Passing two credentials to one client, such as api_key and auth_token, raises ValueError. The CLI's --api-key and --auth-token flags are gone.

I covered how to choose temperature in another article, and the 400s that come with swapping model IDs in the scanner for Sonnet 4.5's retirement. The two scanners look for different things, so running both doesn't double up.

Next step

Run this scanner once on your own repository. If the count is 0, all that's left is choosing the day to upgrade. If it isn't, that number is the size of the job before the switch.

Before you pick the day to switch, count where your code leans on those arguments—that's a line I intend to keep for the next version too.

Sources: API version 2026-10-21 migration

Share

Thank You for Reading

Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⬡ API & SDK2026-10-08
Before Sonnet 4.5 retires, one script to find the arguments that will return a 400
claude-sonnet-4-5 retires on November 30. Swapping the model ID is not enough: budget_tokens, prefill, and tool_choice will fail. Here is a scanner built from the official migration guide, its real output, and the order I would fix things in.
⬡ API & SDK2026-10-13
Haiku 5.5 is cheaper per token, but your bill jumps fivefold past about 77k Haiku 4.5 tokens
On the price sheet, Haiku 5.5 costs roughly a tenth of Haiku 4.5. But the same text becomes about 30% more tokens, and a prompt over 100k tokens is billed at five times the rate. Here is the boundary in Haiku 4.5 terms, a script I actually ran, and the changes that return a 400.
⬡ API & SDK2026-08-14
Move the Prompt Tools API Into Your Own Scripts Before Workbench Closes on August 17
The legacy Workbench and three experimental prompt endpoints shut down on August 17, 2026. Here is how to count what actually depends on them, plus working local replacements for templatize_prompt and improve_prompt with real output.