# 速率限制

MoreLogin 使用网关限流保护 API 稳定性。已确认的生产配置对 API 和 Open API 均为每分钟 200 次、突发容量 10。

## 当前实现契约

| 项目 | 生产环境行为 |
|  --- | --- |
| 算法 | GCRA（通用信元速率算法） |
| 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 请求消耗一个网关许可；条目数量不参与网关限流计算 |


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.