> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# API 速率限制：限额、响应头与 429 退避策略

> Didit API 速率限制、X-RateLimit 响应头、Retry-After 退避及各端点作用域与 429 处理方法。帮助您放心规划生产环境流量。

Didit 对 API 实施多层速率限制，在保证服务稳定的同时，允许合理的流量突发。限制按**每个标识符**应用：即您的 `x-api-key`；若未发送 API 密钥，则为您的客户端 IP。每个范围各自维护独立计数器，采用 60 秒滑动窗口。

## 标识符与豁免

中间件按以下顺序选择速率限制标识符：

1. `x-api-key` 请求头 → 按密钥划分的计数器。
2. `X-Forwarded-For` 的第一跳 → 按 IP 划分的计数器。
3. `REMOTE_ADDR` → 按 IP 划分的计数器。

以下调用**完全绕过**速率限制：

* 内部的 `DIDIT_WEBAPP_API_KEY`（仅供 Didit 控制台使用）。
* 受 `IsValidSession` 权限保护的 endpoint（嵌入式/SDK 会话流量）。
* `GET /system/healthcheck`（在此中间件栈之前由 `HealthMiddleware` 提供服务）。
* 服务商 Webhook：Stripe、AML 筛查、人脸验证回调、KYC 名单变更。

## 全局限制

每个已认证请求还会按 HTTP 方法归入一个通用范围，因此任何新路由都会继承合理的默认值：

| 范围 | HTTP 方法 | 限制 | 窗口 |
| - | - | - | - |
| `generic-get` | `GET` | 600 次/分钟 | 60 秒 |
| `generic-write` | `POST`、`PATCH`、`DELETE` | 300 次/分钟 | 60 秒 |
| `ingest-write` | `POST /v3/transactions/`、`POST /v3/events/`、`POST /v3/events/batch/` | 600 次/分钟 | 60 秒 |

创建交易和事件摄取计入 `ingest-write`，而**不计入** `generic-write`。因此，高吞吐量的摄取集成每个 API 密钥每分钟可获得 600 次请求，且不会占用其其他写操作的额度。

endpoint 专属限制（见下一节）**额外**叠加于全局限制之上。最先超出其计数器的范围会返回 `429`。

## endpoint 专属限制

中间件为影响最大的路由定义了更严格的范围：

| 范围 | endpoint | 限制 | 常量来源 |
| - | - | - | - |
| `session-v2-create` | `POST /v2/session/`、`POST /v3/session/` | 600 次/分钟 | `SESSION_CREATE_RATE_LIMIT` |
| `session-decision` | `GET /v1/session/<id>/decision/`、`GET /v2/session/<id>/decision/` | 100 次/分钟 | `SESSION_DECISION_RATE_LIMIT` |
| `session-generate-pdf` | `GET /v1/session/<id>/generate-pdf/`、`GET /v3/session/<id>/generate-pdf/` | 50 次/分钟 | `SESSION_PDF_RATE_LIMIT` |
| `session-add-images` | `POST`/`PATCH /session/<id>/add-images/` | 10 次/分钟 | `SESSION_ADD_IMAGES_RATE_LIMIT` |
| `session-update-kyc` | `POST`/`PATCH /session/<id>/update-data/` | 10 次/分钟 | `SESSION_UPDATE_KYC_RATE_LIMIT` |
| `session-update-poa` | `POST`/`PATCH /session/<id>/update-poa-data/` | 10 次/分钟 | `SESSION_UPDATE_POA_RATE_LIMIT` |

> [!NOTE]
>
> `GET /v3/session/<id>/decision/` 由 `SessionV3RetrieveView` 提供服务，**不在** `session-decision` 范围内。它仅受 `generic-get` 的 600 次/分钟上限约束。100 次/分钟的决策节流只适用于 v1 和 v2 决策路径。

> [!WARNING]
>
> 代码库中还定义了 `FREE_SESSION_RATE_LIMIT = 10` 和 `PAID_SESSION_RATE_LIMIT = 600`。这些常量**目前未应用于** `POST /v3/session/`。该 endpoint 对所有应用统一使用 600 次/分钟的 `session-v2-create` 限制。在按工作流划分的免费额度节流被记录为已生效之前，请勿据此规划容量。

## 429 响应结构

超出任一范围时，您会收到 HTTP `429` 响应，其响应体和响应头如下：

```http theme={null}
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1762534870
Retry-After: 37

{
  "detail": "Session creation rate limit exceeded. You can make up to 600 requests per minute."
}
```

| 响应头 | 含义 |
| - | - |
| `X-RateLimit-Limit` | 触发限制的范围在当前窗口内允许的最大请求数。 |
| `X-RateLimit-Remaining` | 窗口重置前剩余的请求数。一旦超出限制，将截断为 `0`。 |
| `X-RateLimit-Reset` | 计数器重置为零时的 Unix 纪元秒数。 |
| `Retry-After` | 下次尝试前需等待的秒数，向上取整，确保等待后的下一次尝试必定落在下一个窗口内。**仅出现在 `429` 响应中**，并非每个请求都会携带。 |

`X-RateLimit-*` 响应头仅出现在 `429` 响应中，成功的响应目前不包含这些头。请使用 `Retry-After` 的值（或 `X-RateLimit-Reset - now`）作为下次尝试前的等待时长。

## 客户端指南

* **遵循 `Retry-After`**，将其作为退避时间的下限。在此基础上叠加带抖动的指数退避（`5s → 10s → 20s → 40s ± random`），使同时遇到 `429` 的两个客户端不会同步重试。
* **达到较小的上限后停止重试**（例如 5 次）。将错误向上传递给调用方，并向团队发出告警：持续出现的 429 通常意味着失控的循环，而非瞬时流量峰值。
* **每个工作负载使用一个标识符。** 多个后端共用同一个 `x-api-key` 时，它们会计入同一计数器，其中一个占用大量配额的后端可能导致其他后端配额不足。请为每个环境、品牌或客户分别创建应用（因此也会获得单独的 `api_key`），参见 [程序化注册](https://docs.didit.me/zh/integration/programmatic-registration.md#管理应用)。
* **缓存决策读取结果。** `/decision/` 端点并非为高频轮询设计。如需实时更新，请使用 [Webhook](https://docs.didit.me/zh/integration/webhooks.md)，并且仅在回填或审计时调用 `GET /v3/session/<id>/decision/`。
* **离线生成 PDF。** PDF 渲染受 CPU 限制（上限为每分钟 50 次）。应在会话到达终止状态后，由队列触发生成，而不是在每次页面访问时触发。

## 处理 429 的示例客户端

```javascript theme={null}
async function postSession(body) {
  for (let attempt = 0; attempt < 5; attempt++) {
    const res = await fetch("https://verification.didit.me/v3/session/", {
      method: "POST",
      headers: {
        "x-api-key": process.env.DIDIT_API_KEY,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(body),
    });

    if (res.status !== 429) return res;

    const retryAfter = Number(res.headers.get("Retry-After") ?? "1");
    const jitter = Math.random() * 1000;
    await new Promise((r) => setTimeout(r, retryAfter * 1000 + jitter));
  }
  throw new Error("Didit: too many 429s, giving up");
}
```

```python theme={null}
import os
import random
import time

import httpx

def post_session(body: dict) -> httpx.Response:
    with httpx.Client(timeout=15) as client:
        for _ in range(5):
            resp = client.post(
                "https://verification.didit.me/v3/session/",
                headers={
                    "x-api-key": os.environ["DIDIT_API_KEY"],
                    "Content-Type": "application/json",
                },
                json=body,
            )
            if resp.status_code != 429:
                return resp
            wait_s = float(resp.headers.get("Retry-After", "1")) + random.random()
            time.sleep(wait_s)
    raise RuntimeError("Didit: too many 429s, giving up")
```

## 需要更高配额吗

如果每分钟 600 个会话仍不够用，例如遇到营销活动上线或数据回填，请将您的 `application` UUID 以及预期的持续速率和突发速率发送至 [support@didit.me](mailto:support@didit.me)。配额覆盖设置按应用单独配置。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.