# レート制限

MoreLogin は API を保護するためゲートウェイでレート制限を行います。確認済みの本番設定は API と Open API の両方で毎分 200 回、burst 容量 10 です。

## 現在の実装契約

| 項目 | 本番の挙動 |
|  --- | --- |
| アルゴリズム | GCRA（Generic Cell Rate Algorithm） |
| API の設定 | `rate=200`、`burst=10`、`period=1m` |
| Open API の設定 | `rate=200`、`burst=10`、`period=1m` |
| 継続的な回復 | 0.3 秒ごとに 1 許可、およそ毎秒 3.33 許可 |
| バースト | 最大 10 許可を即時に使用できます |
| Open API のスコープキー | リクエストパス + チーム ID |
| API のスコープキー | リクエストパス + チーム ID。チーム ID が使えない場合は API ID、次にクライアント IP にフォールバックします |
| 実行時の上書き | ルート単位またはチーム単位の API ルールが既定の API 設定を上書きできます |
| バッチリクエスト | HTTP リクエスト 1 回でゲートウェイ許可 1 つを消費します。項目数はゲートウェイの制限計算に使われません |


These are the confirmed production gateway values, not the Java fallback (`rate=1`, `burst=1`, `period=1s`). At `rate=200` over 60 seconds, one permit is restored every 0.3 seconds (approximately 3.33 per second), and up to 10 permits can accumulate. The localhost Local API does not traverse this public cloud gateway.

- `rate`: permits restored during `period`
- `burst`: maximum permits immediately available
- `period`: restoration interval


## 制限時の応答

```json
{
  "code": 35000,
  "msg": "API requests are too frequent. Please try again later.",
  "data": null,
  "requestId": "request-trace-id"
}
```

- The current response writer does not explicitly set HTTP `429`; inspect body `code` even when HTTP status is `200`.
- No `Retry-After`, `RateLimit-*`, or `X-RateLimit-*` headers are currently emitted.
- Handle HTTP `429` defensively for future gateway or edge-proxy changes.
- The message language can vary; branch on `code`, not `msg`.


## 安全な再試行

Retry reads with exponential backoff and jitter. For writes, query current state before retrying. A general public idempotency-key header is not currently documented.

```python
import random
import time
import requests

def api_request_with_retry(url, payload, headers=None, max_retries=4):
    for attempt in range(max_retries):
        response = requests.post(url, json=payload, headers=headers, timeout=30)
        try:
            body = response.json()
        except ValueError:
            body = {}

        limited = response.status_code == 429 or body.get("code") == 35000
        if not limited:
            return response

        retry_after = response.headers.get("Retry-After")
        delay = float(retry_after) if retry_after and retry_after.isdigit() else min(2 ** attempt, 30) + random.uniform(0, 0.5)
        time.sleep(delay)

    raise RuntimeError("Rate limit retry budget exhausted")
```

## リクエスト数の削減

- Prefer batch endpoints when their semantics match the operation.
- タイムゾーン、言語、カーネル、デバイス機種などの参照データをキャッシュしてください。
- Poll asynchronous operations with increasing intervals and stop at a terminal state.
- Do not blindly retry create, purchase, upload-registration, or schedule-creation requests.