はじめに / 対象と前提
Claude API を業務で叩いていると、ある日突然 429 rate_limit_error や 529 overloaded_error が返ってくる。自分もバッチ処理を回している途中で 429 の嵐に遭い、「とりあえず sleep して再実行」で誤魔化していたら、今度はリトライが二重に走って余計に制限を食い潰す、という悪循環にハマった。
この記事は以下の読者向け。
- Python で Claude API(Anthropic API)を呼んでいて、429 / 529 への対処を「なんとなく sleep」で済ませている人
- SDK の自動リトライ仕様を把握しないままラッパーを自作しようとしている人
環境:Python 3.13 / anthropic SDK 1.x(2026年時点)
TL;DR
- 429(レート制限)と 529(過負荷)は原因も対処も別物。429 は自分の使用量の問題、529 は Anthropic 側の混雑
- Python SDK は デフォルトで 429 / 5xx を 2 回まで自動リトライしている(
max_retries=2)。知らずに自前リトライを重ねると二重リトライになる - 429 のときは
retry-afterヘッダーとanthropic-ratelimit-*ヘッダーを読めば「あと何秒待てばいいか」「どの制限に当たったか」が分かる
手順 / 動かし方
1. まず typed exception で 429 と 529 を区別する
SDK は HTTP ステータスごとに例外クラスを分けている。文字列マッチではなくクラスで捕まえるのが基本。
import anthropic
client = anthropic.Anthropic()
try:
msg = client.messages.create(
model="claude-opus-5",
max_tokens=16000,
messages=[{"role": "user", "content": "..."}],
)
except anthropic.RateLimitError as e: # 429 のみ
print("rate limit:", e.message)
except anthropic.APIStatusError as e: # その他の非2xx(529 含む)
if e.type == "overloaded_error": # ← .type で 529 を判別
print("overloaded (529)")
else:
print(e.status_code, e.type)
except anthropic.APIConnectionError as e: # レスポンス前のネットワーク断
print("connection error")
ポイントは 2 つ。
-
RateLimitErrorが捕まえるのは 429 だけ。529 は 5xx 系なのでInternalServerError(APIStatusErrorの子)側に落ちる - 例外オブジェクトの
.typeプロパティで API のエラー種別文字列("rate_limit_error"/"overloaded_error"など)が取れるので、ステータスコードより細かい分類ができる
2. レスポンスヘッダーで「どの制限に当たったか」を確認する
429 は RPM(リクエスト/分)・TPM(トークン/分)・TPD(トークン/日)のどれかを超えたときに返る。どれに当たったかはヘッダーで分かる。
resp = client.messages.with_raw_response.create(
model="claude-opus-5",
max_tokens=1024,
messages=[{"role": "user", "content": "ping"}],
)
h = resp.headers
print(h.get("anthropic-ratelimit-requests-remaining")) # RPM 残り
print(h.get("anthropic-ratelimit-tokens-remaining")) # トークン残り
print(h.get("anthropic-ratelimit-requests-reset")) # リセット時刻(RFC 3339)
message = resp.parse() # 通常の Message オブジェクトに戻す
with_raw_response を挟むと生のレスポンスが取れる。正常時からこのヘッダーを監視しておけば、429 を食らう前に送信ペースを落とせる。
3. リトライは SDK に任せるのが第一選択
client = anthropic.Anthropic(max_retries=5) # デフォルトは 2
SDK は 408 / 409 / 429 / 5xx と接続エラーを指数バックオフ付きで自動リトライする。429 に retry-after ヘッダーが付いていればそれも尊重する。大抵のケースはこの max_retries を増やすだけで足りる。自前で tenacity や while ループを書くのは、リトライ間の副作用(進捗保存・ログ・キュー返却)が必要になってからでいい。
ハマりどころ
ハマりどころ1:SDK の「隠れ自動リトライ」で二重リトライになる
自分が最初に踏んだのがこれ。tenacity で 3 回リトライを巻いたら、実際のリクエスト数は最大 3 × (1 + 2) = 9 回になっていた。SDK がデフォルトで 2 回リトライしている上に、外側のリトライが重なるからだ。
回避策:自前リトライを書くなら SDK 側を max_retries=0 にして役割を一本化する。逆に SDK に任せるなら外側には何も書かない。また、タイムアウトも各リトライに個別に適用されるので、体感の待ち時間は最悪 timeout × (max_retries + 1) まで伸びる。「処理が固まった」と思ったら実はリトライ中だった、はよくある。
ハマりどころ2:529 を 429 と同じ扱いにしてしまう
529 は overloaded_error、つまり Anthropic 側の過負荷で、自分の使用量とは無関係。429 対策として「送信ペースを半分にする」ロジックを 529 にも適用すると、意味なくスループットを落とすことになる。
回避策:529 は「時間を置いて指数バックオフで再試行」が正解。retry-after が付く保証もない。頻発するなら status.anthropic.com を確認しつつ、負荷の低いモデル(Haiku 系)への一時フォールバックやリクエストのキューイングを検討する。429 とは分岐を分けておくこと。
ハマりどころ3:TPM は「推定入力トークン」で先に消費される
TPM の消費はレスポンスが返ってから確定するのではなく、リクエスト受付時に推定入力トークン分が先に引かれる。だから長大なコンテキスト(大きなシステムプロンプトや履歴全体)を並列で投げると、リクエスト数は少ないのに TPM 側の 429 が出る。
回避策:並列実行するときは anthropic-ratelimit-tokens-remaining を見てセマフォを絞る。事前に client.messages.count_tokens() で入力サイズを見積もっておくと、1 リクエストで TPM の何割を食うのかが分かる。
まとめ
- 429 と 529 は例外クラスと
.typeで明確に分岐する(RateLimitErrorは 429 専用) - リトライはまず SDK の
max_retriesに任せる。自前リトライを書くならmax_retries=0で二重化を防ぐ -
retry-afterとanthropic-ratelimit-*ヘッダーを読めば、待ち時間と超過した制限の種類が分かる - TPM は推定入力トークンで先取り消費される。並列時は残量ヘッダーで送信を絞る
エラーを「とりあえず sleep」で潰すのをやめてヘッダーを読むようにしただけで、バッチの安定性は目に見えて変わった。同じところでハマっている人の参考になれば。