◉CLAUDE LABEN
●2.1.301 — vim の % モーション、VS Code パネルでの LaTeX 数式表示、使用量上限待ちのリトライ監視の上限が入りました(10/15)●2.1.299 — 200K トークン超で約54分放置した会話はアイドル圧縮。MCP の許可ルールは名指ししたサーバーだけに一致します●SONNET 4.5 — Claude API での提供終了は 11/30、残り45日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST MODE — Opus 5.5 の Fast mode がリサーチプレビューを終え、ベータヘッダーが不要になりました(10/15)●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました●2.1.301 — vim の % モーション、VS Code パネルでの LaTeX 数式表示、使用量上限待ちのリトライ監視の上限が入りました(10/15)●2.1.299 — 200K トークン超で約54分放置した会話はアイドル圧縮。MCP の許可ルールは名指ししたサーバーだけに一致します●SONNET 4.5 — Claude API での提供終了は 11/30、残り45日。移行先は Sonnet 5.5●WINDOWS — 日本語の環境でだけフックが黙って壊れる報告があり、文字コードの確認が呼びかけられています●FAST MODE — Opus 5.5 の Fast mode がリサーチプレビューを終え、ベータヘッダーが不要になりました(10/15)●NEW — Cowork に渡したフォルダの先頭に、十行の約束ファイルを置いてみました
記事一覧/API & SDK
⬡ API & SDK/2026-03-09中級

Claude API エラーハンドリングとリトライ戦略

Claude APIのHTTPステータスコードとエラータイプ、レート制限の仕組みを整理し、本番で安定した連携を実現するリトライ戦略を解説します。SDKの自動リトライ機能、サーキットブレーカーパターンの実装、ストリーミング時のエラー処理まで、堅牢な統合のための実践をまとめました。

API27エラーハンドリング7リトライ5レート制限6SDK4

Claude API エラーハンドリングとリトライ戦略

堅牢で信頼性の高い API 統合を構築するには、適切なエラーハンドリングとリトライ戦略が不可欠です。このガイドでは、Claude API で発生する可能性のあるエラーの種類、その対処方法、そして本番環境での最適なプラクティスを整理します。

HTTP ステータスコードとエラー対応

Claude API から返される HTTP ステータスコードと、それぞれの対応方法を理解する点が肝心です。

HTTP ステータスコード一覧

ステータスコード名称説明対応方法
400Bad Requestリクエストパラメータが無効リクエストの内容を確認し修正
401UnauthorizedAPI キーが無効、またはなしAPI キーの設定を確認
403Forbiddenリクエストの実行が許可されていないアカウントの権限を確認
404Not Found要求されたリソースが存在しないエンドポイントや model ID を確認
429Too Many Requestsレート制限に達したバックオフとリトライを実装
500Internal Server Errorサーバーの内部エラーリトライを実行
529Service UnavailableAPI サーバーが過負荷状態エクスポーネンシャルバックオフでリトライ

エラーレスポンス形式

Claude API のすべてのエラーレスポンスは、標準化されたフォーマットで返されます:

{
  "type": "error",
  "error": {
    "type": "error_type_here",
    "message": "Error message describing what went wrong"
  }
}

エラーレスポンスの例:

{
  "type": "error",
  "error": {
    "type": "invalid_request_error",
    "message": "max_tokens must be between 1 and 4096"
  }
}

エラータイプと対応方法

エラータイプ一覧

エラータイプHTTP ステータス説明推奨対応
invalid_request_error400リクエストパラメータが無効コードを修正してリトライしない
authentication_error401認証に失敗API キーを確認
permission_error403操作が許可されていないアカウント権限を確認
not_found_error404リソースが見つからないリソース ID を確認
rate_limit_error429レート制限に達したバックオフしてリトライ
api_error500サーバーの内部エラーリトライを実行
overloaded_error529サーバーが過負荷エクスポーネンシャルバックオフ

エラータイプ別対応フロー

graph TD
    A[エラー受信] --> B{エラータイプ?}
    B -->|400| C[リトライなし<br/>コードを修正]
    B -->|401/403| D[認証/権限確認<br/>リトライなし]
    B -->|404| E[リソースID確認<br/>リトライなし]
    B -->|429| F[バックオフして<br/>リトライ]
    B -->|500| G[リトライ実行<br/>指数バックオフ]
    B -->|529| H[長めの待機で<br/>リトライ]

重要な注意: 400、401、403、404 のエラーはコードの問題を示しており、リトライしても成功しません。必ずコードを修正してから再実行してください。

レート制限の理解と対応

Claude API ではレート制限が設定されており、過度なリクエストを防いでいます。

レート制限パラメータ

  • RPM (Requests Per Minute): 1 分間に送信できるリクエスト数の上限
  • TPM (Tokens Per Minute): 1 分間に処理できるトークン数の上限

レート制限に達すると、HTTP 429 ステータスとともに rate_limit_error が返されます。

レスポンスヘッダーに含まれる情報:

RateLimit-Limit-Requests: 50
RateLimit-Limit-Tokens: 90000
RateLimit-Remaining-Requests: 10
RateLimit-Remaining-Tokens: 45000
RateLimit-Reset-Requests: 2026-03-08T12:34:56Z
RateLimit-Reset-Tokens: 2026-03-08T12:34:56Z

これらのヘッダーを監視することで、リトライのタイミングをより正確に計画できます。

リトライ戦略の実装

エクスポーネンシャルバックオフの基本

リトライは無限に実行してはいけません。エクスポーネンシャルバックオフを使用することで、API サーバーの負荷を軽減しながら、効率的にリトライします。

基本的な公式:

wait_time = min(base_delay * (2 ^ retry_count), max_delay) + jitter

Python での実装

以下は Python でエクスポーネンシャルバックオフを実装する例です:

import time
import random
from typing import TypeVar, Callable, Any
import anthropic
 
T = TypeVar('T')
 
def retry_with_exponential_backoff(
    func: Callable[..., T],
    max_retries: int = 3,
    base_delay: float = 1.0,
    max_delay: float = 60.0,
) -> T:
    """
    エクスポーネンシャルバックオフを使用してリトライを実行します。
 
    Args:
        func: 実行する関数
        max_retries: 最大リトライ回数
        base_delay: 初期待機時間(秒)
        max_delay: 最大待機時間(秒)
 
    Returns:
        関数の戻り値
 
    Raises:
        例外: 最大リトライ回数に達した場合
    """
    for attempt in range(max_retries + 1):
        try:
            return func()
        except anthropic.RateLimitError:
            if attempt == max_retries:
                raise
 
            # エクスポーネンシャルバックオフ + ジッター
            delay = min(base_delay * (2 ** attempt), max_delay)
            jitter = random.uniform(0, delay * 0.1)
            wait_time = delay + jitter
 
            print(f"Rate limited. Retrying in {wait_time:.2f} seconds...")
            time.sleep(wait_time)
        except anthropic.APIStatusError as e:
            if e.status_code == 500 or e.status_code == 529:
                if attempt == max_retries:
                    raise
 
                delay = min(base_delay * (2 ** attempt), max_delay)
                jitter = random.uniform(0, delay * 0.1)
                wait_time = delay + jitter
 
                print(f"Server error ({e.status_code}). Retrying in {wait_time:.2f} seconds...")
                time.sleep(wait_time)
            else:
                # リトライできないエラー
                raise
 
# 使用例
def call_api():
    client = anthropic.Anthropic()
    return client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=1024,
        messages=[
            {"role": "user", "content": "Hello, Claude!"}
        ]
    )
 
response = retry_with_exponential_backoff(call_api)
print(response)

TypeScript での実装

以下は TypeScript でのリトライ実装です:

import Anthropic from "@anthropic-ai/sdk";
 
interface RetryOptions {
  maxRetries?: number;
  baseDelay?: number;
  maxDelay?: number;
}
 
async function retryWithExponentialBackoff<T>(
  fn: () => Promise<T>,
  options: RetryOptions = {}
): Promise<T> {
  const {
    maxRetries = 3,
    baseDelay = 1000, // ミリ秒
    maxDelay = 60000,
  } = options;
 
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (attempt === maxRetries) {
        throw error;
      }
 
      const isRetryableError =
        (error instanceof Anthropic.RateLimitError) ||
        (error instanceof Anthropic.APIStatusError &&
          (error.status === 500 || error.status === 529));
 
      if (!isRetryableError) {
        throw error;
      }
 
      const delay = Math.min(
        baseDelay * Math.pow(2, attempt),
        maxDelay
      );
      const jitter = Math.random() * delay * 0.1;
      const waitTime = delay + jitter;
 
      console.log(
        `Retryable error encountered. Waiting ${waitTime.toFixed(2)}ms before retry...`
      );
      await new Promise((resolve) => setTimeout(resolve, waitTime));
    }
  }
}
 
// 使用例
async function main() {
  const client = new Anthropic();
 
  const message = await retryWithExponentialBackoff(async () => {
    return client.messages.create({
      model: "claude-3-5-sonnet-20241022",
      max_tokens: 1024,
      messages: [
        {
          role: "user",
          content: "Hello, Claude!",
        },
      ],
    });
  });
 
  console.log(message.content);
}
 
main().catch(console.error);

SDK の自動リトライ機能

Anthropic Python と TypeScript SDK には、自動リトライ機能が組み込まれています。

Python SDK

Python SDK は以下の状況で自動的にリトライします:

  • HTTP 429 (Rate Limit Error)
  • HTTP 500 (Internal Server Error)
  • HTTP 529 (Overloaded Error)

デフォルトの最大リトライ回数は 3 回です:

import anthropic
 
client = anthropic.Anthropic()
 
# SDK が自動的にリトライを処理
message = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=1024,
    messages=[
        {"role": "user", "content": "Hello, Claude!"}
    ]
)

TypeScript SDK

TypeScript SDK でも同様に自動リトライが機能します:

import Anthropic from "@anthropic-ai/sdk";
 
const client = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY,
});
 
// SDK が自動的にリトライを処理
const message = await client.messages.create({
  model: "claude-3-5-sonnet-20241022",
  max_tokens: 1024,
  messages: [
    {
      role: "user",
      content: "Hello, Claude!",
    },
  ],
});

ヒント: SDK の自動リトライに頼れない場合は、カスタムリトライロジックを実装できます。特に複雑な要件がある場合や、リトライ時の追加処理が必要な場合に有効です。

サーキットブレーカーパターンの実装

連続したエラーが発生している場合、API へのリクエストを一時的に停止することで、サーバーの負荷軽減を助け、リソースを節約できます。サーキットブレーカーパターンはこれを実現します。

Python でのサーキットブレーカー実装

from enum import Enum
from datetime import datetime, timedelta
from typing import Callable, TypeVar
 
T = TypeVar('T')
 
class CircuitState(Enum):
    """サーキットブレーカーの状態"""
    CLOSED = "closed"      # 正常状態、リクエストを許可
    OPEN = "open"          # エラー状態、リクエストを拒否
    HALF_OPEN = "half_open"  # 回復テスト中
 
class CircuitBreaker:
    """
    サーキットブレーカーの実装
    """
    def __init__(
        self,
        failure_threshold: int = 5,
        success_threshold: int = 2,
        timeout: int = 60,
    ):
        self.failure_threshold = failure_threshold
        self.success_threshold = success_threshold
        self.timeout = timeout
 
        self.failure_count = 0
        self.success_count = 0
        self.last_failure_time = None
        self.state = CircuitState.CLOSED
 
    def call(self, func: Callable[..., T], *args, **kwargs) -> T:
        """
        サーキットブレーカーを通じて関数を実行
        """
        if self.state == CircuitState.OPEN:
            if self._should_attempt_reset():
                self.state = CircuitState.HALF_OPEN
                self.success_count = 0
            else:
                raise Exception("Circuit breaker is OPEN. Too many failures.")
 
        try:
            result = func(*args, **kwargs)
            self._on_success()
            return result
        except Exception as e:
            self._on_failure()
            raise
 
    def _on_success(self):
        """成功時の処理"""
        self.failure_count = 0
 
        if self.state == CircuitState.HALF_OPEN:
            self.success_count += 1
            if self.success_count >= self.success_threshold:
                self.state = CircuitState.CLOSED
                print("Circuit breaker closed - system recovered")
 
    def _on_failure(self):
        """失敗時の処理"""
        self.failure_count += 1
        self.last_failure_time = datetime.now()
 
        if self.failure_count >= self.failure_threshold:
            self.state = CircuitState.OPEN
            print("Circuit breaker opened - too many failures")
 
    def _should_attempt_reset(self) -> bool:
        """リセット試行の判定"""
        if self.last_failure_time is None:
            return False
 
        elapsed = datetime.now() - self.last_failure_time
        return elapsed >= timedelta(seconds=self.timeout)
 
# 使用例
import anthropic
 
breaker = CircuitBreaker(failure_threshold=3, timeout=30)
 
def call_claude_api():
    client = anthropic.Anthropic()
    return client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=1024,
        messages=[
            {"role": "user", "content": "Hello, Claude!"}
        ]
    )
 
try:
    response = breaker.call(call_claude_api)
    print(response)
except Exception as e:
    print(f"Error: {e}")

TypeScript でのサーキットブレーカー実装

enum CircuitState {
  CLOSED = "closed",
  OPEN = "open",
  HALF_OPEN = "half_open",
}
 
interface CircuitBreakerOptions {
  failureThreshold?: number;
  successThreshold?: number;
  timeout?: number; // ミリ秒
}
 
class CircuitBreaker {
  private state: CircuitState = CircuitState.CLOSED;
  private failureCount: number = 0;
  private successCount: number = 0;
  private lastFailureTime: Date | null = null;
 
  private failureThreshold: number;
  private successThreshold: number;
  private timeout: number;
 
  constructor(options: CircuitBreakerOptions = {}) {
    this.failureThreshold = options.failureThreshold ?? 5;
    this.successThreshold = options.successThreshold ?? 2;
    this.timeout = options.timeout ?? 60000;
  }
 
  async call<T>(fn: () => Promise<T>): Promise<T> {
    if (this.state === CircuitState.OPEN) {
      if (this.shouldAttemptReset()) {
        this.state = CircuitState.HALF_OPEN;
        this.successCount = 0;
      } else {
        throw new Error("Circuit breaker is OPEN. Too many failures.");
      }
    }
 
    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }
 
  private onSuccess(): void {
    this.failureCount = 0;
 
    if (this.state === CircuitState.HALF_OPEN) {
      this.successCount++;
      if (this.successCount >= this.successThreshold) {
        this.state = CircuitState.CLOSED;
        console.log("Circuit breaker closed - system recovered");
      }
    }
  }
 
  private onFailure(): void {
    this.failureCount++;
    this.lastFailureTime = new Date();
 
    if (this.failureCount >= this.failureThreshold) {
      this.state = CircuitState.OPEN;
      console.log("Circuit breaker opened - too many failures");
    }
  }
 
  private shouldAttemptReset(): boolean {
    if (!this.lastFailureTime) {
      return false;
    }
 
    const elapsed = Date.now() - this.lastFailureTime.getTime();
    return elapsed >= this.timeout;
  }
}
 
// 使用例
import Anthropic from "@anthropic-ai/sdk";
 
const breaker = new CircuitBreaker({
  failureThreshold: 3,
  timeout: 30000,
});
 
const client = new Anthropic();
 
async function main() {
  try {
    const response = await breaker.call(async () => {
      return client.messages.create({
        model: "claude-3-5-sonnet-20241022",
        max_tokens: 1024,
        messages: [
          {
            role: "user",
            content: "Hello, Claude!",
          },
        ],
      });
    });
 
    console.log(response.content);
  } catch (error) {
    console.error("Error:", error);
  }
}
 
main();

ストリーミングエラーハンドリング

ストリーミングレスポンスを使用する場合、通常のリクエストとは異なるエラーハンドリングが必要です。

ストリーミング特有のエラー

  • 接続エラー: ネットワーク接続が切断される
  • ストリームエラー: イベントストリーム中にエラーが発生
  • タイムアウト: ストリーム受信のタイムアウト

Python でのストリーミングエラーハンドリング

import anthropic
 
def stream_with_error_handling():
    """ストリーミングレスポンスをエラーハンドリング付きで処理"""
    client = anthropic.Anthropic()
 
    try:
        with client.messages.stream(
            model="claude-3-5-sonnet-20241022",
            max_tokens=1024,
            messages=[
                {"role": "user", "content": "Write a short story"}
            ],
        ) as stream:
            for text in stream.text_stream:
                print(text, end="", flush=True)
 
    except anthropic.APIConnectionError as e:
        print(f"Connection error: {e}")
        # 再接続ロジックを実装
 
    except anthropic.APIStatusError as e:
        if e.status_code == 429:
            print("Rate limited while streaming")
        elif e.status_code >= 500:
            print("Server error during streaming")
        else:
            print(f"API error: {e.status_code}")
 
    except Exception as e:
        print(f"Unexpected error: {e}")
 
stream_with_error_handling()

TypeScript でのストリーミングエラーハンドリング

import Anthropic from "@anthropic-ai/sdk";
 
async function streamWithErrorHandling() {
  const client = new Anthropic();
 
  try {
    const stream = await client.messages.stream({
      model: "claude-3-5-sonnet-20241022",
      max_tokens: 1024,
      messages: [
        {
          role: "user",
          content: "Write a short story",
        },
      ],
    });
 
    for await (const chunk of stream) {
      if (
        chunk.type === "content_block_delta" &&
        chunk.delta.type === "text_delta"
      ) {
        process.stdout.write(chunk.delta.text);
      }
    }
  } catch (error) {
    if (error instanceof Anthropic.APIConnectionError) {
      console.error("Connection error:", error.message);
      // 再接続ロジックを実装
    } else if (error instanceof Anthropic.APIStatusError) {
      if (error.status === 429) {
        console.error("Rate limited while streaming");
      } else if (error.status >= 500) {
        console.error("Server error during streaming");
      } else {
        console.error(`API error: ${error.status}`);
      }
    } else {
      console.error("Unexpected error:", error);
    }
  }
}
 
streamWithErrorHandling();

ベストプラクティス

1. タイムアウト設定

無限に待機しないよう、タイムアウトを設定してください:

import anthropic
 
client = anthropic.Anthropic(timeout=30.0)  # 30秒でタイムアウト
import Anthropic from "@anthropic-ai/sdk";
 
const client = new Anthropic({
  timeout: 30 * 1000, // 30秒でタイムアウト
});

2. エラーログの実装

エラーを詳細にログに記録することで、問題の診断に役立ちます:

import logging
 
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
 
def log_error_details(error, attempt, wait_time):
    logger.error(
        f"API call failed",
        extra={
            "error_type": type(error).__name__,
            "error_message": str(error),
            "attempt": attempt,
            "wait_time": wait_time,
        }
    )

3. リトライ可能なエラーの識別

すべてのエラーがリトライ可能とは限りません。正しく識別しましょう:

def is_retryable(error):
    """リトライ可能なエラーかどうかを判定"""
    if isinstance(error, anthropic.RateLimitError):
        return True
 
    if isinstance(error, anthropic.APIStatusError):
        return error.status_code in [500, 529]
 
    return False

4. リクエストのキャンセル機能

長時間実行されるリクエストをキャンセルできるようにしましょう:

import threading
 
def call_api_with_timeout(timeout_seconds=30):
    result = [None]
    exception = [None]
 
    def api_call():
        try:
            client = anthropic.Anthropic()
            result[0] = client.messages.create(...)
        except Exception as e:
            exception[0] = e
 
    thread = threading.Thread(target=api_call)
    thread.daemon = True
    thread.start()
    thread.join(timeout=timeout_seconds)
 
    if thread.is_alive():
        raise TimeoutError("API call exceeded timeout")
 
    if exception[0]:
        raise exception[0]
 
    return result[0]

トラブルシューティング

よくある問題と解決策

問題: 429 エラーが頻繁に発生する

  • 解決: リトライの待機時間を増やす、リクエスト頻度を下げる、リトライ回数の上限を増やす

問題: 500 エラーが続く

  • 解決: API ステータスページを確認、サポートに問い合わせ

問題: タイムアウトが発生する

  • 解決: タイムアウト値を増やす、リクエストを簡略化、max_tokens を減らす

問題: ストリーム接続が切断される

  • 解決: 接続を自動的に再試行する、チャンク単位で処理できるようにする

全体を振り返って

効果的なエラーハンドリングとリトライ戦略は、堅牢で信頼性の高い API 統合の基礎となります。このガイドで紹介した技術を適切に組み合わせることで、本番環境での問題を最小化し、優れたユーザー体験を実現できます。

重要なポイントをおさらい:

  • エラータイプに応じた適切な対応
  • エクスポーネンシャルバックオフを使用したリトライ
  • サーキットブレーカーパターンでサーバー負荷軽減
  • ストリーミング特有のエラーハンドリング
  • 詳細なログ記録とモニタリング
シェア

お読みいただきありがとうございます

Claude Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

⬡ API & SDK2026-10-14
retry-after が 120 秒の 429 を受けると、Anthropic SDK の自動リトライは 120 秒待とうとしました
Anthropic SDK の自動リトライを模擬サーバーで12通り確かめました。既定は2回まで、retry-after は上限なしで従い、上限到達の429は待っても通りません。待ち時間と総時間に上限を持つ呼び出し口のコードと実測ログを載せます。
⬡ API & SDK2026-07-24
メモリストアの棚卸しが半分しか返らない — path_prefix セグメント一致への移行
agent-memory-2026-07-22 で memories.list が変わりました。順序固定・depth 厳格化・path_prefix のセグメント一致。件数が音もなく半減した実例と、破壊的変更に耐える棚卸し層、呼び出し箇所を ast で洗い出す方法、depth=1 再帰の走査コストの実測をまとめます。
⬡ API & SDK2026-05-02
Claude API の月額予算を破綻させない設計 — 個人開発者のための実践コスト管理
個人開発で Claude API を組み込んだとき、最初にぶつかるのは月末の請求書です。月額予算を守り抜くための見積もり・モニタリング・上限設定・リトライ設計を、自分のアプリで躓いた箇所と一緒にまとめました。