Skip to content
言語: 日本語

開発者向けAPI

レート制限:すべての上限とヘッダー

2026年11月1日更新 約7分で読めます

LinkHarbor APIで1分あたりに許可されるリクエスト数、リクエストの最大サイズ、ヘッダーの読み方、429の適切な扱い方を説明します。

すべての人にとってAPIを高速に保つため、LinkHarborは1分あたりのリクエスト数を制限しています。上限はプランに依存せず、API呼び出しの月間の枠もありません。この記事では、すべての上限を示し、コードでそれらをきれいに扱う方法を説明します。

リクエストの数え方

  • 各リクエストはちょうど1つのバケットに入ります(例:認証済みリクエストはuser、トークンの交換はtoken)
  • カウントは1分(UTC)単位で、新しい1分ごとにカウンターはゼロから始まります
  • バケットによって、APIはワークスペースごと、人ごと、接続ごと、IPアドレスごとに数えます
  • トークン、スコープ、スキーマの段階ですでに失敗するリクエスト(401、403 insufficient_scope、400、スキーマチェックによる422 validation_failed)は数えられません。403 feature_not_availableなど、その後のエラーは数えられます

すべての上限

バケット(X-RateLimit-Bucket)上限カウント単位対象
user1分あたり600APIキーのトークンではワークスペース(ワークスペースのすべてのキーとAIツールの合計)、ダッシュボードのログインでは人専用のバケットを持たない/v1へのすべての認証済みリクエスト
token1分あたり30IPアドレスPOST /v1/token
public1分あたり120IPアドレスGET /v1/plansやGET /v1/pages/availableなど、認証なしのAPIへのリクエスト
callback1分あたり120IPアドレスPOST /v1/callbacks/sale
internal1分あたり120IPアドレス「Custom (API)」連携のIngest URL(POST /v1/ingest/{id})
mcp1分あたり120AI接続(アクセストークン)/mcpのMCPサーバー
oauth1分あたり300IPアドレスAIアシスタント向けのOAuthログインとディスカバリー
auth1分あたり30IPアドレスログイン、登録、パスワードのリセット(ダッシュボードとアプリ)
refresh1分あたり120IPアドレスダッシュボードのセッションの延長
discover1分あたり60人、ログインしていない場合はIPアドレスアプリの「見つける」セクション
subs_read1分あたり120人、ログインしていない場合はIPアドレスフォロー中のページの読み取り(アプリ)
subs_write1分あたり20人、ログインしていない場合はIPアドレスフォロー中のページの変更(アプリ)

ワークスペースごとに1つの枠

同じワークスペースのAPIキーのトークンはすべて、キーをいくつ作成しても、1分あたり600件のリクエストを共有します。接続したAIアシスタントのツールも内部でAPIを呼び出すため、ここに含まれます。そのため、キーを増やしても上限は上がりません。複数のプログラムに大きな負荷が分散する場合は、共有の枠を前提に計画してください。

ヘッダー

X-RateLimit-Limit
バケットの1分あたりの上限です(例:600)。
X-RateLimit-Remaining
現在の1分間に残っているリクエスト数です。
X-RateLimit-Bucket
リクエストが入ったバケットです(例:user)。
Retry-After
429 rate_limitedの場合のみ:次の1分が始まるまでの秒数です。api_key_lockedの場合、待ち時間はdetails.retry_after_secにあります。

上限に達したとき

上限に達すると、APIはHTTP 429と次の本文を返します。messageのテキストはアカウント言語で(APIのエラー形式を参照)、ここでは英語で示しています。コードrate_limitedを頼りにしてください。

429のレスポンス
HTTP/1.1 429 Too Many Requests
Retry-After: 37
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-RateLimit-Bucket: user

{ "error": "rate_limited", "message": "Too many requests. Please try again in a moment.", "retry_after_sec": 37 }
  1. Retry-Afterまたはretry_after_secを読み取ります
  2. すべてのプロセスが同時に再開しないよう、少なくともその秒数に少しランダムな時間を加えて待ちます
  3. リクエストの再試行は数回までにします
  4. 429が続く場合は、作業をより長い時間に分散させます
Node.js:トークンをキャッシュして429を処理する
const BASE = 'https://dock.at-harbor.com';
let cached = null; // { token, expiresAt }

async function getToken() {
  if (cached && Date.now() < cached.expiresAt - 60_000) return cached.token;
  const res = await fetch(BASE + '/v1/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      key_id: process.env.LH_KEY_ID,
      key_secret: process.env.LH_KEY_SECRET,
    }),
  });
  if (!res.ok) throw new Error('Token exchange failed: HTTP ' + res.status);
  const data = await res.json();
  cached = { token: data.token, expiresAt: Date.now() + data.expires_in * 1000 };
  return cached.token;
}

async function lh(method, path, body, attempt = 0) {
  const headers = { Authorization: 'Bearer ' + (await getToken()) };
  if (body !== undefined) headers['Content-Type'] = 'application/json';

  const res = await fetch(BASE + path, {
    method,
    headers,
    body: body === undefined ? undefined : JSON.stringify(body),
  });

  if (res.status === 429 && attempt < 3) {
    const wait = Number(res.headers.get('Retry-After') ?? 5);
    await new Promise((r) => setTimeout(r, (wait + Math.random() * 2) * 1000));
    return lh(method, path, body, attempt + 1);
  }
  if (res.status === 401 && attempt === 0) {
    cached = null; // トークンの期限切れ – 新しいトークンでもう一度試す
    return lh(method, path, body, attempt + 1);
  }
  const data = await res.json().catch(() => null);
  if (!res.ok) throw new Error((data && data.error) + ' (HTTP ' + res.status + ')');
  return data;
}

// 例: const { items } = await lh('GET', '/v1/pages');

大量のデータを扱うコツ

  • トークンをキャッシュし、15分の期限が切れる少し前にだけ更新します
  • X-RateLimit-Remainingを監視し、残りが少なくなったら速度を落とします
  • 数十の並列接続ではなく、リクエストを1つずつ順番に送ります
  • 各ページを個別に問い合わせるのではなく、リストを一度読み込んでローカルで絞り込みます
  • 大きな同期は数分に分散させます
  • 5xxエラーは間隔を広げながら再試行しますが、4xxエラーは再試行しません

その他の上限

対象上限レスポンス
ログインの失敗メールアドレスとIPごとに5回、10回、20回の失敗でロック:60秒、5分、30分429 account_locked
フォームの送信すべてのフォームを合わせて、IPアドレスごとに1時間あたり10件429 form_rate_limited

トークン交換の失敗は、キーとIPを一時的にロックします。APIキーとアクセストークンをご覧ください。プッシュ通知の送信はダッシュボードでのみ行い、その上限はフォロワーとプッシュ通知で説明しています。

ページ、リンク、ブロック、短縮リンクなど、プランのコンテンツの上限はレート制限ではなく、固定の上限です。プランの比較に記載しています。

リクエストのサイズ

すべての本文は、APIが読み取る前にサイズが測定されます。許容される大きさはエンドポイントによって異なります。

エンドポイント本文の最大サイズ
ログイン、登録、/v1/auth以下のその他の認証なしの呼び出し、POST /v1/token、POST /v1/oauth/exchange、/oauth以下のAIアシスタント向けOAuthエンドポイントとその同意、POST /v1/ingest/{id}、「応援する」ブロックの決済、アプリのサブスクリプション関連の呼び出し(フォロー、コレクション、プッシュ用デバイス)、ワークスペースの切り替え、POST /v1/auth/logout-all、アカウントの削除、二要素認証の設定、招待の承認、紹介プログラムの有効化、連携の接続時の戻りのステップ64 KB
ページ上のフォームの送信を含む、その他のすべてのJSONエンドポイント1 MB
POST /v1/webhooks/{id}/verify-signatureと/mcpのMCPサーバー2 MB
ファイルのアップロード(POST /v1/assetsとPOST /v1/pages/{id}/uploads、multipart/form-data)リクエスト全体で11 MB、ファイル自体は最大10 MB

本文がこれより大きい場合、APIは413 request_too_largeを返し、details.max_bytesとdetails.max_kbに上限が示されます。アップロードでファイルが大きすぎる場合は413 payload_too_largeになります。DELETEの呼び出しなど本文のないエンドポイントは、本文なしか{}だけを受け付けます。

413のレスポンス
HTTP/1.1 413 Payload Too Large

{ "error": "request_too_large", "message": "…", "details": { "max_bytes": 65536, "max_kb": 64 }, "request_id": "…" }

本文の中にも上限があります。各テキストには最大長、各リストには最大件数があり、不明なフィールドは拒否されます。値はAPIリファレンスにエンドポイントごとに記載しています。違反すると422 validation_failedになり、detailsにフィールド、ルール、上限が示されます。リクエストのパスとクエリ文字列は、合わせて最大16 KBまでです。

答えが見つかりませんか?

お困りのことをお知らせください。サポートチームが個別にお手伝いします。

サポートに問い合わせる