When I first read the Haiku 5.5 price sheet, input at $0.10 per million tokens, I assumed I had dropped a digit. Haiku 4.5 was $1.00, so I looked twice.
I had not misread it. But the same table has another row: any request whose input goes over 100,000 tokens is billed at five times that rate. Starting a migration with "it got cheaper" in my head felt like setting myself up for the opposite surprise, so I ran the numbers by hand first.
What I'd like to say up front is that the boundary is not 100k if you are counting in Haiku 4.5 tokens. It is about 77k, because the tokenizer changed.
The official numbers (checked on October 13, 2026)
| Item | Haiku 4.5 | Haiku 5.5 |
|---|---|---|
| Model ID | claude-haiku-4-5 | claude-haiku-5-5 (no dated ID) |
| Input / output per MTok | $1 / $5 | $0.10 / $0.50 (input up to 100k) |
| Requests over 100k | Same rate | $0.50 / $2.50 (5x) |
| Cache read | $0.10 | $0.01 ($0.05 over 100k) |
| Context / max output | 200k / 64k | 1M / 128k |
| Tokens for the same text | baseline | about 30% more |
Two details are easy to miss. The 100k check counts cache reads and writes too, and it is applied per request, so a cache hit does not save you if the total goes over.
Computing the boundary
I turned the official numbers into a function that takes the token count you were seeing on Haiku 4.5 and returns the cost on 5.5. The output below is from actually running this script. Nothing was sent to the API; it is arithmetic only.
"""Haiku 4.5 -> 5.5: 同じ呼び出しの1リクエスト単価を比べる(USD / 100万トークン)"""
INFLATE = 1.30 # 公式: 同じ文章が約30%多いトークンになる
P45 = dict(inp=1.00, out=5.00, cache=0.10)
P55_LO = dict(inp=0.10, out=0.50, cache=0.01) # 入力全体が 100,000 以下
P55_HI = dict(inp=0.50, out=2.50, cache=0.05) # 100,000 超(5倍)
THRESH = 100_000
def cost45(fresh, cached, out):
p = P45
return (fresh*p["inp"] + cached*p["cache"] + out*p["out"]) / 1e6
def cost55(fresh, cached, out):
fresh, cached, out = fresh*INFLATE, cached*INFLATE, out*INFLATE
# 判定は cache 読み書きを含む入力の合計
p = P55_HI if (fresh + cached) > THRESH else P55_LO
return (fresh*p["inp"] + cached*p["cache"] + out*p["out"]) / 1e6, p is P55_HI
if __name__ == "__main__":
print(f"{'4.5換算の入力':>12} {'4.5':>9} {'5.5':>9} {'比':>6} 段")
for n in (10_000, 50_000, 70_000, 76_000, 77_000, 80_000, 120_000):
a = cost45(n, 0, 1000)
b, hi = cost55(n, 0, 1000)
print(f"{n:>12,} {a:9.5f} {b:9.5f} {b/a:6.2f} {'上段' if hi else '下段'}")
print("境目(4.5換算):", f"{THRESH/INFLATE:,.0f}")
# 境目をまたぐ前後で 5.5 自身の単価がどれだけ跳ねるか
lo = cost55(76_900, 0, 1000)[0]; hi = cost55(77_000, 0, 1000)[0]
print(f"76,900 -> 77,000 : {lo:.5f} -> {hi:.5f} ({hi/lo:.2f}倍)")
# 同じ 90,000(4.5換算) をキャッシュ読み 60,000 + 新規 30,000 で送る場合
print("cache込み 90,000:", f"4.5={cost45(30_000,60_000,1000):.5f}", f"5.5={cost55(30_000,60_000,1000)[0]:.5f}")Output:
4.5換算の入力 4.5 5.5 比 段
10,000 0.01500 0.00195 0.13 下段
50,000 0.05500 0.00715 0.13 下段
70,000 0.07500 0.00975 0.13 下段
76,000 0.08100 0.01053 0.13 下段
77,000 0.08200 0.05330 0.65 上段
80,000 0.08500 0.05525 0.65 上段
120,000 0.12500 0.08125 0.65 上段
境目(4.5換算): 76,923
76,900 -> 77,000 : 0.01065 -> 0.05330 (5.01倍)
cache込み 90,000: 4.5=0.04100 5.5=0.02665(The column labels are in Japanese: input in 4.5 terms, 4.5 cost, 5.5 cost, ratio, and lower/upper tier.)
Two things differed from what I expected.
- Even past the boundary, 5.5 stays cheaper than 4.5 (the ratio moves from 0.13 to 0.65). Migrating does not cost you money.
- Compared with itself, though, 5.5 jumps 5.01x between 76,900 and 77,000. Trimming a few hundred tokens from a prompt can cut that request's cost to a fifth.
"It's cheaper, so I can stop worrying" and "it's worth staying under the line" were both true.
What returns a 400
A request that doesn't run doesn't bill, so here is the other half. According to the official migration guide, code that worked on 4.5 changes like this:
| On 4.5 | On 5.5 |
|---|---|
thinking: {"type": "enabled", "budget_tokens": N} | 400. Use adaptive with output_config.effort |
temperature / top_p / top_k | Remove them. Any top_k is a 400; top_p must be 0.99 |
| Trailing assistant message (prefill) | 400, even with thinking off. End on a user turn |
computer_20250124 | 400. Use computer_toolset_20260801 |
| Sending thinking blocks back after editing system, tools, or earlier turns | 400 |
The scanner I wrote for Sonnet 5.5 works almost as is; add patterns for the top_p rule and the computer-use tool.
One more catch: thinking tokens count toward max_tokens. A limit that was comfortable on 4.5 can stop right after a thinking block with stop_reason: "max_tokens" and an empty answer.
resp = client.messages.create(
model="claude-haiku-5-5",
max_tokens=4000, # leave headroom even if 4.5 used 1000
thinking={"type": "adaptive"},
output_config={"effort": "low"}, # start light tasks at low
messages=[{"role": "user", "content": text}],
)
# Reading that survives a leading thinking block
answer = next((b.text for b in resp.content if b.type == "text"), "")
if resp.stop_reason == "max_tokens" and not answer:
raise RuntimeError("Hit the limit inside thinking; raise max_tokens")As an indie developer calling the API from small scripts, I'll apply this to my own calls first, since any place that reads the first content block as the answer is the most likely to break.
The order I'd work in
- Pull the input-token distribution from your 4.5 logs and count how many calls exceed 77k.
- For those, see whether the prompt can shrink (stale history, duplicated documents) before anything else. That removes the fivefold step.
- Fix the 400 items and give
max_tokensheadroom. - Once it runs, lower
effortto find the quality and cost balance.
Priority Tier is not supported on Haiku 5.5, so if your setup relied on it, capacity needs a separate estimate.
One thing to try today
Take the top 1% of your input sizes from the logs and check whether any exceed 77k. If none do, you can skip most of this article.
The low price list only applies cleanly while you stay on the near side of the line. That's the check I'd like to make first on every new model.
Sources: Claude Haiku 5.5 migration guide, What's new in Claude Haiku 5.5, Pricing