メインコンテンツへスキップ
#AI活用 約8分で読めます

Anthropic API rate limit 対策【2026年】Claude API 安定運用の実装パターン

Claude API のレート制限を理解し、リトライ処理・バックオフ・キューイングで安定運用を実現する実装ガイド。エラーハンドリング設計で API 障害を最小化

Claude API を本番環境で運用する際、避けて通れないのが レート制限(rate limit) への対応です。API 呼び出しが集中すると 429 Too Many Requests エラーが発生し、サービス停止につながるリスクがあります。

この記事では、Anthropic API のレート制限の仕組みを正確に理解し、Exponential Backoff やキューイングを用いた堅牢な実装パターンを解説します。単なるリトライ処理ではなく、本番運用に耐える設計を目指します。

Anthropic API のレート制限の仕組み

Anthropic API は Tier ベースのレート制限 を採用しています。利用量に応じて Tier が上がり、制限が緩和される仕組みです。

Tier ごとの制限値

2026年5月時点の主要 Tier の制限は以下の通りです(公式ドキュメントを参照):

Tier条件RPM (Requests Per Minute)TPM (Tokens Per Minute)TPD (Tokens Per Day)
Freeサインアップ直後520,000300,000
Build Tier 1$5 以上の支払い実績5040,0001,000,000
Build Tier 2$40 以上の支払い実績1,00080,0002,500,000
Build Tier 3$200 以上の支払い実績2,000160,0005,000,000
Build Tier 4$1,000 以上の支払い実績4,000400,00010,000,000

重要ポイント:

  • RPM(リクエスト数制限)と TPM(トークン数制限)の両方を満たす必要がある
  • TPD は日次の累積制限で、超過すると翌日までリクエスト不可
  • モデルごとに制限が異なる(Opus, Sonnet, Haiku で共通の枠を消費)

レート制限エラーの読み方

レート制限に達すると、API は以下のような 429 レスポンスを返します:

{
  "error": {
    "type": "rate_limit_error",
    "message": "Rate limit exceeded"
  }
}

ヘッダーにリトライ推奨時間が含まれる場合があります:

retry-after: 20
x-ratelimit-requests-remaining: 0
x-ratelimit-tokens-remaining: 12450
x-ratelimit-requests-reset: 2026-05-09T12:34:56Z

この情報を活用することで、効率的なリトライ処理を実装できます。

Exponential Backoff によるリトライ戦略

レート制限エラーに対する基本戦略は Exponential Backoff(指数バックオフ) です。

基本的な実装パターン

async function callClaudeWithRetry(
  apiKey: string,
  prompt: string,
  maxRetries = 5
): Promise<string> {
  let attempt = 0;

  while (attempt < maxRetries) {
    try {
      const response = await fetch("https://api.anthropic.com/v1/messages", {
        method: "POST",
        headers: {
          "x-api-key": apiKey,
          "anthropic-version": "2023-06-01",
          "content-type": "application/json",
        },
        body: JSON.stringify({
          model: "claude-sonnet-4-5-20250929",
          max_tokens: 1024,
          messages: [{ role: "user", content: prompt }],
        }),
      });

      if (response.status === 429) {
        // レート制限エラー
        const retryAfter = response.headers.get("retry-after");
        const waitTime = retryAfter
          ? parseInt(retryAfter, 10) * 1000
          : Math.pow(2, attempt) * 1000 + Math.random() * 1000; // Jitter 付き

        console.log(`Rate limited. Retrying after ${waitTime}ms`);
        await sleep(waitTime);
        attempt++;
        continue;
      }

      if (!response.ok) {
        throw new Error(`API error: ${response.status}`);
      }

      const data = await response.json();
      return data.content[0].text;
    } catch (error) {
      if (attempt === maxRetries - 1) throw error;
      attempt++;
      await sleep(Math.pow(2, attempt) * 1000);
    }
  }

  throw new Error("Max retries exceeded");
}

function sleep(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

実装のポイント:

  • retry-after ヘッダーがあれば優先的に使用
  • ない場合は 2^attempt * 1000ms で待機時間を指数的に増加
  • Jitter(ランダムなずれ) を追加して、リトライが集中しないようにする
  • 最大リトライ回数を設定して無限ループを防ぐ

Jitter の重要性

Jitter がない場合、複数のクライアントが同時にリトライすると、再び同時刻にリクエストが集中してしまいます(Thundering Herd Problem)。

// 悪い例: Jitter なし
const waitTime = Math.pow(2, attempt) * 1000; // 全クライアントが同じタイミングでリトライ

// 良い例: Jitter あり
const waitTime = Math.pow(2, attempt) * 1000 + Math.random() * 1000;

キューイングによる並列リクエスト制御

レート制限を超えないようにするには、事前にリクエストを制限するアプローチも有効です。

シンプルなキュー実装

class ClaudeAPIQueue {
  private queue: Array<() => Promise<void>> = [];
  private processing = false;
  private requestsPerMinute: number;
  private requestTimes: number[] = [];

  constructor(requestsPerMinute: number) {
    this.requestsPerMinute = requestsPerMinute;
  }

  async enqueue<T>(fn: () => Promise<T>): Promise<T> {
    return new Promise((resolve, reject) => {
      this.queue.push(async () => {
        try {
          const result = await fn();
          resolve(result);
        } catch (error) {
          reject(error);
        }
      });

      if (!this.processing) {
        this.processQueue();
      }
    });
  }

  private async processQueue() {
    this.processing = true;

    while (this.queue.length > 0) {
      await this.waitIfNeeded();

      const task = this.queue.shift();
      if (task) {
        this.requestTimes.push(Date.now());
        await task();
      }
    }

    this.processing = false;
  }

  private async waitIfNeeded() {
    const now = Date.now();
    const oneMinuteAgo = now - 60000;

    // 1分以内のリクエストをカウント
    this.requestTimes = this.requestTimes.filter((time) => time > oneMinuteAgo);

    if (this.requestTimes.length >= this.requestsPerMinute) {
      const oldestRequest = this.requestTimes[0];
      const waitTime = 60000 - (now - oldestRequest) + 100; // 100ms のバッファ
      console.log(`Queue waiting ${waitTime}ms to respect rate limit`);
      await sleep(waitTime);
    }
  }
}

// 使用例
const queue = new ClaudeAPIQueue(50); // Build Tier 1 の RPM 制限

async function callClaudeWithQueue(prompt: string): Promise<string> {
  return queue.enqueue(async () => {
    const response = await fetch("https://api.anthropic.com/v1/messages", {
      // ... (前述の fetch 設定)
    });
    const data = await response.json();
    return data.content[0].text;
  });
}

p-queue を使った実装

より高機能なキュー管理には p-queue ライブラリが便利です:

import PQueue from "p-queue";

const queue = new PQueue({
  concurrency: 5, // 同時実行数
  interval: 60000, // 1分
  intervalCap: 50, // 1分間に最大50リクエスト
});

async function callClaude(prompt: string): Promise<string> {
  return queue.add(async () => {
    const response = await fetch(/* ... */);
    return response.json();
  });
}

レート制限を考慮したアーキテクチャ設計

本番環境では、キューイングとリトライを組み合わせた多層防御が推奨されます。

flowchart TD
    A[クライアント] --> B[リクエストキュー]
    B --> C{RPM制限内?}
    C -->|Yes| D[API呼び出し]
    C -->|No| E[待機]
    E --> C
    D --> F{成功?}
    F -->|200 OK| G[レスポンス返却]
    F -->|429 Rate Limit| H[Exponential Backoff]
    H --> I{リトライ回数上限?}
    I -->|No| D
    I -->|Yes| J[エラー返却]
    F -->|500 Server Error| K[即座にリトライ]
    K --> I

図: レート制限を考慮した API 呼び出しフロー。キューイングで事前制御し、429 エラーには Backoff で対応する

レート制限情報の永続化

複数サーバーで API を共有する場合、Redis などでレート制限状態を共有します:

import Redis from "ioredis";

class DistributedRateLimiter {
  private redis: Redis;
  private key: string;
  private limit: number;
  private window: number; // ミリ秒

  constructor(redis: Redis, key: string, limit: number, windowMs: number) {
    this.redis = redis;
    this.key = key;
    this.limit = limit;
    this.window = windowMs;
  }

  async acquire(): Promise<boolean> {
    const now = Date.now();
    const windowStart = now - this.window;

    const pipeline = this.redis.pipeline();
    
    // 古いエントリを削除
    pipeline.zremrangebyscore(this.key, 0, windowStart);
    
    // 現在のウィンドウ内のリクエスト数を取得
    pipeline.zcard(this.key);
    
    // 新しいリクエストを追加
    pipeline.zadd(this.key, now, `${now}-${Math.random()}`);
    
    // キーの有効期限を設定
    pipeline.expire(this.key, Math.ceil(this.window / 1000));

    const results = await pipeline.exec();
    const count = results?.[1]?.[1] as number;

    return count < this.limit;
  }
}

// 使用例
const redis = new Redis();
const limiter = new DistributedRateLimiter(
  redis,
  "claude-api-requests",
  50, // RPM
  60000 // 1分
);

async function callClaudeDistributed(prompt: string): Promise<string> {
  const allowed = await limiter.acquire();
  
  if (!allowed) {
    throw new Error("Rate limit exceeded across cluster");
  }

  // API 呼び出し処理
  return callClaudeWithRetry(apiKey, prompt);
}

Token Per Minute (TPM) 制限への対応

RPM だけでなく、トークン数の累積制限(TPM) にも注意が必要です。

トークン数の事前推定

function estimateTokens(text: string): number {
  // 簡易推定: 英語は約4文字/token、日本語は約2文字/token
  const asciiChars = text.match(/[\x00-\x7F]/g)?.length || 0;
  const nonAsciiChars = text.length - asciiChars;
  
  return Math.ceil(asciiChars / 4 + nonAsciiChars / 2);
}

class TokenRateLimiter {
  private tokensPerMinute: number;
  private tokenUsage: Array<{ timestamp: number; tokens: number }> = [];

  constructor(tokensPerMinute: number) {
    this.tokensPerMinute = tokensPerMinute;
  }

  canSendRequest(estimatedTokens: number): boolean {
    const now = Date.now();
    const oneMinuteAgo = now - 60000;

    // 1分以内のトークン使用量を集計
    this.tokenUsage = this.tokenUsage.filter((u) => u.timestamp > oneMinuteAgo);
    const currentUsage = this.tokenUsage.reduce((sum, u) => sum + u.tokens, 0);

    return currentUsage + estimatedTokens <= this.tokensPerMinute;
  }

  recordUsage(tokens: number) {
    this.tokenUsage.push({ timestamp: Date.now(), tokens });
  }
}

// 使用例
const tokenLimiter = new TokenRateLimiter(40000); // Build Tier 1

async function callClaudeWithTokenLimit(prompt: string): Promise<string> {
  const estimatedTokens = estimateTokens(prompt) + 1024; // max_tokens を加算

  if (!tokenLimiter.canSendRequest(estimatedTokens)) {
    throw new Error("Token rate limit would be exceeded");
  }

  const result = await callClaudeWithRetry(apiKey, prompt);
  
  // 実際の使用量を記録(レスポンスヘッダーから取得)
  tokenLimiter.recordUsage(estimatedTokens);
  
  return result;
}

エラーハンドリングのベストプラクティス

レート制限以外のエラーも適切に処理する必要があります。

エラータイプごとの処理

class ClaudeAPIError extends Error {
  constructor(
    message: string,
    public statusCode: number,
    public errorType: string,
    public retryable: boolean
  ) {
    super(message);
  }
}

async function callClaudeWithErrorHandling(prompt: string): Promise<string> {
  try {
    const response = await fetch(/* ... */);
    
    if (!response.ok) {
      const error = await response.json();
      
      switch (response.status) {
        case 429:
          throw new ClaudeAPIError(
            "Rate limit exceeded",
            429,
            "rate_limit_error",
            true
          );
        case 500:
        case 529:
          throw new ClaudeAPIError(
            "Server error",
            response.status,
            "server_error",
            true
          );
        case 400:
          throw new ClaudeAPIError(
            error.error.message,
            400,
            "invalid_request_error",
            false
          );
        default:
          throw new ClaudeAPIError(
            error.error.message,
            response.status,
            error.error.type,
            false
          );
      }
    }
    
    return await response.json();
  } catch (error) {
    if (error instanceof ClaudeAPIError && error.retryable) {
      // リトライ可能なエラー
      console.log(`Retryable error: ${error.message}`);
      throw error;
    } else {
      // リトライ不可能なエラー
      console.error(`Non-retryable error: ${error}`);
      throw error;
    }
  }
}

ロギングとモニタリング

class APIMetrics {
  private requests = 0;
  private rateLimitErrors = 0;
  private serverErrors = 0;
  private successfulRequests = 0;

  recordRequest() {
    this.requests++;
  }

  recordRateLimitError() {
    this.rateLimitErrors++;
  }

  recordServerError() {
    this.serverErrors++;
  }

  recordSuccess() {
    this.successfulRequests++;
  }

  getStats() {
    return {
      totalRequests: this.requests,
      rateLimitErrors: this.rateLimitErrors,
      serverErrors: this.serverErrors,
      successfulRequests: this.successfulRequests,
      successRate: this.requests > 0 ? this.successfulRequests / this.requests : 0,
    };
  }
}

const metrics = new APIMetrics();

async function callClaudeWithMetrics(prompt: string): Promise<string> {
  metrics.recordRequest();
  
  try {
    const result = await callClaudeWithRetry(apiKey, prompt);
    metrics.recordSuccess();
    return result;
  } catch (error) {
    if (error instanceof ClaudeAPIError) {
      if (error.statusCode === 429) {
        metrics.recordRateLimitError();
      } else if (error.statusCode >= 500) {
        metrics.recordServerError();
      }
    }
    throw error;
  }
}

// 定期的にメトリクスを出力
setInterval(() => {
  console.log("API Metrics:", metrics.getStats());
}, 60000);

まとめ

Claude API のレート制限に対する堅牢な運用には、以下の対策が必要です:

  • Tier ベースの制限を理解する — RPM・TPM・TPD の三重制限を把握
  • Exponential Backoff + Jitter — 429 エラー時の効率的なリトライ
  • キューイングによる事前制御 — レート制限を超えないようリクエストを調整
  • 分散環境での状態共有 — Redis などで複数サーバー間のレート制限を管理
  • トークン数の推定と管理 — TPM 制限を超えないようトークン使用量を監視
  • エラータイプごとの適切な処理 — リトライ可能/不可能を判定
  • メトリクス収集とモニタリング — レート制限エラーの発生率を監視

これらのパターンを組み合わせることで、本番環境でも安定した Claude API 運用が実現できます。

参考リンク

#Claude #API #レート制限 #エラーハンドリング #安定運用
シェア