Zenovay APIのレート制限を理解し、信頼性とパフォーマンスを最適化する方法をご説明します。
レート制限の概要
REST APIは有料機能です。APIキーはPro、Scale、Enterpriseプランのみで動作し、Freeプランのキーは403 API_PAID_PLAN_REQUIREDレスポンスで拒否されます。
プラン別のAPI制限
これらの制限は/api/external/v1/配下のREST APIエンドポイントに適用されます:
| プラン | リクエスト/分 | リクエスト/月 |
|---|---|---|
| Pro | 30 | 10,000 |
| Scale | 60 | 100,000 |
| Enterprise | 120 | 1,000,000 |
制限はチームの購読内容から解決されます。複数のチームに属している場合、APIはあなたが属する最高のプランを使用します。
リクエストとしてカウントされるもの
認証されたすべてのAPIコールは月間クォータに対して1リクエストとしてカウントされます — 読み取りエンドポイント(GET)とクエリエンドポイント(POST /query/:websiteId)です。REST APIは読み取り専用です。PUT、PATCH、DELETEエンドポイントはありません。
カウントされないもの
認証に失敗したリクエスト(401、例えば不足または無効なキー)はカウントされません。キーが検証されないためです。
レート制限ヘッダー
レスポンスヘッダー
すべてのAPIレスポンスに使用状況ヘッダーが含まれます:
X-RateLimit-Limit: 30
X-Usage-Monthly: 4521
X-Usage-Limit: 10000
X-Usage-Reset: 2026-07-01T00:00:00.000Z
X-Usage-Total: 89234
| ヘッダー | 説明 |
|---|---|
| X-RateLimit-Limit | 1分あたりのリクエスト上限 |
| X-RateLimit-Remaining | この1分間の残りリクエスト数(常にあるとは限りません — 以下を参照) |
| X-Usage-Monthly | 今月使用したリクエスト数 |
| X-Usage-Limit | 月間リクエスト上限 |
| X-Usage-Reset | 月間使用量がリセットされる日時(ISO 8601) |
| X-Usage-Total | このキーで行われたリクエストの合計 |
X-RateLimit-Remainingは正確な残りリクエスト数が利用可能な場合にのみ含まれます。その不在は正常として扱い、予算計画にはX-RateLimit-Limitと独自のリクエスト数に頼ってください。
ヘッダーの読み取り
const response = await fetch(url, { headers });
const limit = response.headers.get('X-RateLimit-Limit');
const remaining = response.headers.get('X-RateLimit-Remaining'); // null の可能性あり — 上記注参照
const monthlyReset = response.headers.get('X-Usage-Reset'); // ISO 8601タイムスタンプ
console.log(`残り ${remaining ?? '?'}/${limit} リクエスト`);
console.log(`月間使用量は${monthlyReset}にリセットされます`);
レート制限超過時のレスポンス
分単位の制限を超えた場合:
HTTP/1.1 429 Too Many Requests
Retry-After: 42
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded (30 requests/minute). Try again in 42 seconds",
"timestamp": "2026-06-13T12:00:00.000Z"
}
}
分単位の429にはRetry-Afterヘッダー(秒単位)が含まれ、再試行までの待機時間を指示します。
月間クォータを超えた場合、エラー本文は同じ形式で"code": "MONTHLY_LIMIT_EXCEEDED"を使用します。このレスポンスにはRetry-Afterヘッダーがありません — 代わりにX-Usage-Resetヘッダーを確認してください。月間割り当てがリセットされるときを示します(翌月の1日)。
429エラーの処理
async function apiRequest(url, options, retries = 3) {
const response = await fetch(url, options);
if (response.status === 429 && retries > 0) {
const retryAfter = response.headers.get('Retry-After') || 60;
console.log(`Rate limited. Waiting ${retryAfter}s...`);
await sleep(retryAfter * 1000);
return apiRequest(url, options, retries - 1);
}
return response;
}
ベストプラクティス
キャッシュの活用
可能な限りレスポンスをキャッシュします:
const cache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5分
async function getWithCache(endpoint) {
const cached = cache.get(endpoint);
if (cached && Date.now() - cached.time < CACHE_TTL) {
return cached.data;
}
const response = await api.get(endpoint);
cache.set(endpoint, { data: response, time: Date.now() });
return response;
}
必要なデータのみ取得
rangeフィルタを使用してデータを制限します:
# 特定の時間範囲にフィルター
GET /api/external/v1/analytics/WEBSITE_ID?range=7d
日付フィルタを使用する
返されるデータを特定の期間に制限します(サポートされている値:24h、7d、30d、90d、1y):
# 過去30日のみ取得
GET /api/external/v1/analytics/WEBSITE_ID?range=30d
ページネーション
訪問者エンドポイントはlimitとoffsetでページネーションされます(ページ番号ではありません)。limitのデフォルトは100で、1000に制限されます。
リクエスト
GET /api/external/v1/analytics/WEBSITE_ID/visitors?range=30d&limit=100&offset=0
レスポンス
{
"success": true,
"data": {
"visitors": [...],
"pagination": {
"limit": 100,
"offset": 0,
"has_more": true
}
},
"timestamp": "2026-06-13T12:00:00.000Z"
}
効率的なページネーション
has_moreがfalseになるまでページを要求し続け、毎回offsetをlimitだけ増やします:
async function getAllVisitors(websiteId) {
let allVisitors = [];
let offset = 0;
const limit = 100;
let hasMore = true;
while (hasMore) {
const response = await api.get(
`/analytics/${websiteId}/visitors?limit=${limit}&offset=${offset}`
);
const { visitors, pagination } = response.data;
allVisitors = allVisitors.concat(visitors);
hasMore = pagination.has_more;
offset += limit;
// レート制限を尊重する
await sleep(100);
}
return allVisitors;
}
指数バックオフ
実装例
async function requestWithBackoff(fn, maxRetries = 5) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (error.status === 429 && attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
console.log(`Retry ${attempt + 1} after ${delay}ms`);
await sleep(delay);
} else {
throw error;
}
}
}
}
バックオフスケジュール
| 試行回数 | 待機時間 |
|---|---|
| 1 | 1〜2秒 |
| 2 | 2〜4秒 |
| 3 | 4〜8秒 |
| 4 | 8〜16秒 |
| 5 | 16〜32秒 |
使用状況の監視
現在の使用状況を確認
curl https://api.zenovay.com/api/external/v1/usage \
-H "X-API-Key: zv_YOUR_API_KEY"
現在の月間リクエスト数、残りクォータ、分単位のレート制限、および購読レベルが返されます。
すべてのAPIレスポンスには、ヘッダー(X-Usage-Monthly、X-Usage-Limit、X-Usage-Reset)にライブ使用状況も含まれているため、別の呼び出しなしに消費を追跡できます。
ダッシュボード内
APIキー(app.zenovay.com/api-keys)を開いて、キーと各キーのリクエスト合計を確認し、最近のアクティビティを表示します。正確な月間数と残りクォータについては、上記のGET /usageエンドポイントを使用してください。
大量アクセスの最適化
集計エンドポイントを活用
複数の個別訪問者レコードをページネーションするのではなく、アナリティクス概要エンドポイントを使用して1回の呼び出しで概要合計を取得します:
# 1回のリクエストでアナリティクス概要を取得
GET /api/external/v1/analytics/WEBSITE_ID?range=30d
このレスポンスにはsummaryブロック(訪問者合計、ページビュー、ユニーク訪問者)とdaily_statsが1つのレスポンスで含まれます。地理的、ページ別、またはデバイス別の詳細については、専用の/countries、/pages、/technologyエンドポイントを使用してください。
エンタープライズのレート制限
Enterprise プランエンタープライズプランは最高レベルの公開されている制限から始まります(120リクエスト/分、1,000,000リクエスト/月)。より高い制限が必要な場合は、以下を[email protected]へ送信してください:
- 現在の使用パターン
- 予想される成長
- ユースケースの詳細
トラブルシューティング
頻繁にレート制限に達する場合
分単位の制限に頻繁に達する場合:
- リクエスト間に遅延を追加する(またはリクエストキューを追加する)
- 429sで指数バックオフを使用する
- 再利用するレスポンスをキャッシュする
- 多くの小さな呼び出しではなく集計エンドポイントを使用する
- より高い制限のためにプランをアップグレードする
月間クォータ枯渇
MONTHLY_LIMIT_EXCEEDED(429)は、月間リクエスト割り当てを使い切ったことを意味します。翌月の1日にリセットされます(X-Usage-Resetヘッダーを参照)。より高い月間クォータについてはプランをアップグレードしてください。
予期せず低い制限
制限が不正に見える場合:
- プランレベルを確認してください — 制限はチームの購読内容に基づいています
- 正しいチームのキーを使用していることを確認してください
- サポートに問い合わせてください