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 | サインアップ直後 | 5 | 20,000 | 300,000 |
| Build Tier 1 | $5 以上の支払い実績 | 50 | 40,000 | 1,000,000 |
| Build Tier 2 | $40 以上の支払い実績 | 1,000 | 80,000 | 2,500,000 |
| Build Tier 3 | $200 以上の支払い実績 | 2,000 | 160,000 | 5,000,000 |
| Build Tier 4 | $1,000 以上の支払い実績 | 4,000 | 400,000 | 10,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 運用が実現できます。