> ## 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.

# 创建监控 token | Didit Authentication API

> Didit 的 POST /monitoring-token/ 接口可将访问 token 换取为 15 分钟有效的设备凭证，并绑定到用户的 vendor_data 和浏览器来源。

> [!WARNING]
>
> 认证功能是新功能，在 Didit 开启此接口之前，它保持关闭状态：在此之前，接口会返回 `503 monitoring_unavailable`。

浏览器端和原生 SDK 从用户设备调用此接口，您的服务器无需调用。随该凭证发送的每个事件都会记录在用户的 `vendor_data` 中，无论设备声称了什么。


## OpenAPI

````yaml zh/openapi-authentication.json POST /monitoring-token/
openapi: 3.0.3
info:
  title: Didit Authentication API
  version: 1.0.0
  description: >-
    Didit Authentication 面向服务器的 endpoint，该产品是为您应用的终端用户提供的登录产品。Authentication
    为**新功能**，将于 2026-11-01 版本在生产环境中正式推出。


    ## 基础 URL


    每个应用都是其独立的 OpenID Connect 签发方（issuer），以下所有 endpoint 均在该签发方的根路径下提供服务：


    - 生产环境应用为 `https://<name>.auth.didit.me`，沙盒应用为
    `https://<name>.sandbox.auth.didit.me`。

    - 您的自定义域名（一旦激活）。激活后，它将取代默认主机作为签发方：新 token 会在 `iss`
    中标明该域名，发现文档（discovery）会指向它，并且可发布密钥也会改变。在默认主机下签发的 access token 将无法用于调用
    `/session/`、`/step-up/` 和 `/monitoring-token/`，直至完成刷新。


    生产环境与沙盒是独立的应用，因此它们是拥有独立密钥的不同签发方。可发布密钥为 `pk_live_`（生产环境）或
    `pk_test_`（沙盒），后接签发方去除末尾斜杠后的无填充 base64url 编码：对其解码即可找到签发方。它是一个公开标识符，并非凭据。


    相同的 JSON endpoint 也在
    `https://apx.didit.me/auth/v2/authentication/{application_id}/`
    下提供。无论由哪个地址签发，token 始终会将上述签发方声明在内。


    ## 凭据


    这些 endpoint 绝不接受您的 Didit API 密钥或控制台 token。


    - 发现文档（Discovery）、JWKS 以及 `POST /token/` 不需要任何凭据。客户端为公开客户端：不存在 client
    secret，且授权码（authorization code）与其 PKCE 验证器（`S256`）绑定。`client_id` 即为您的应用 ID。

    - `/session/`、`/step-up/` 和 `/monitoring-token/` 接收用户的 access
    token：`Authorization: Bearer
    <access_token>`。每次成功的调用均计为该会话的活动，因此会重置会话的空闲超时时间。


    ## 请求体


    所有接收请求体的 endpoint 均支持 JSON、表单编码（form-encoded）和 multipart 请求体。本文档列出了 JSON，以及
    OAuth 客户端发送 `POST /token/` 时使用的表单编码。如果 endpoint 收到不支持的字段，将返回 `400
    {"<field>": "unknown_field"}`。


    ## Token


    - **Access token**：请求头为 `typ: at+jwt` 的 ES256 JWT，有效期最多 5
    分钟，且绝不超过其会话时长。Claims 为 `AccessTokenClaims`。

    - **ID token**：请求头为 `typ: JWT` 的 ES256 JWT。Claims 为 `IDTokenClaims`。绝不能作为
    access token 使用。

    - **Refresh token**：随机字符串，每次使用时均会轮换。再次发送已使用的 refresh token 将会撤销该会话。


    使用来自 `GET /.well-known/jwks.json` 的密钥验证 token：算法必须为 `ES256`，`typ` 必须匹配 token
    类型，`iss` 必须等于您配置的签发方，且 `aud` 必须为您的应用 ID。离线验证仅在 access token
    过期后才能察觉已撤销的会话；`GET /session/` 则是即时检查手段。


    ## 错误


    | 响应体 | 适用情况 |

    | --- | --- |

    | `{"error": "<code>"}` | 来自 `POST /token/` 的错误，状态码均为 `400`，响应体格式符合 [RFC
    6749 第 5.2
    节](https://www.rfc-editor.org/rfc/rfc6749#section-5.2)：`invalid_grant`、`invalid_client`
    以及 Didit 的
    `mfa_required`。服务错误：`temporarily_unavailable`、`monitoring_unavailable`、`monitoring_disabled`。
    |

    | `{"<field>": ["<message>"]}` 或 `{"<field>": "<code>"}` |
    请求字段未通过验证。`non_field_errors` 表示跨字段规则校验失败，`unknown_field` 表示 endpoint 不接受的字段。
    |

    | `{"detail": "<message>"}` |
    `401`（`invalid_token`）、`403`（`origin_not_allowed`）、`404`（未知应用或 step-up）以及
    `429`（速率限制）。此外还包括无法解析请求体的 `400`（`JSON parse error - ...`）、endpoint 不支持该方法的
    `405` 以及不支持的 `Content-Type` 对应的 `415`。 |


    `401` 响应还会携带 `WWW-Authenticate: Bearer`，而 `429` 响应会携带以秒为单位的 `Retry-After`。


    有两种拒绝响应不是 JSON 格式。非签发方地址在到达这些 endpoint 之前就会被拒绝，例如未使用的 `*.auth.didit.me`
    名称会返回空白的 `404`。若 `step_up_id` 不是 UUID，则会返回 `404` 页面。


    ## 速率限制


    请求必须通过两层限制。


    **Endpoint 限制**，按客户端 IP 地址、应用及 endpoint 分别计数：


    - 发现文档（Discovery）与 JWKS：每分钟 1,200 次请求，每小时 12,000 次请求。

    - 其他所有 endpoint：每分钟 30 次请求，每小时 300 次请求。`GET` 和 `DELETE /session/` 共享同一个计数。


    超出限制时返回：`429 {"detail": "Too many requests from this IP address. Please wait
    before trying again. Expected available in 60 seconds."}`，小时限制则为 `3600
    seconds`。


    **Bot 防护**，适用于所有 `POST` 和 `DELETE` 请求。其桶由该应用的所有 `POST` 和 `DELETE`
    请求共享（包括托管登录步骤），并持续补充：


    - 每个客户端 IP 地址每分钟 60 次请求。

    - 每个设备每分钟 30 次请求（适用于像浏览器和原生 SDK 那样标识自身设备的请求）。

    - 整个应用（涵盖所有 IP 地址）每分钟 3,000 次请求。Token 刷新（带有 `grant_type=refresh_token` 的
    `POST /token/`）则使用独立的桶：整个应用每分钟 6,000 次。


    超出桶容量时返回：`429 {"detail": "rate_limited Expected available in N seconds."}`（当
    N 为 1 时为 `1 second.`）。基于 IP 的桶跨 endpoint 共享，因此混合调用 token、step-up
    和退出登录调用的单一地址可能会在触及任何 endpoint 限制之前就耗尽其额度。应用级别的桶会统计该应用的所有客户端，包括浏览器。


    两层限制均在 endpoint 执行前进行拦截并返回响应，因此收到 `429` 的请求并未实际执行：请在 `Retry-After`
    指定的时间之后重试（包括刷新请求）。


    ## 浏览器请求


    携带 `Origin` 请求头的请求必须来自应用自身的源或其允许的源，否则将收到 `403
    origin_not_allowed`。服务器到服务器的请求不发送 `Origin`。


    ## 此处未涵盖的内容


    以下路由用于托管页面、浏览器和原生 SDK 以及 AI agent。它们不属于服务器约定的一部分，服务器 SDK 也不会调用它们：


    - 登录步骤：`POST /authorize/`、`POST
    /password/`、`/email/*`、`/phone/*`、`/password-reset/*`、`/passkeys/*`、`/mfa/*`、`/social/*`
    和 `/protection/complete/`。

    -
    托管页面和回调：`/sign-in`、`/magic`、`/invite`、`/reset`、`/verify/`、`/sso/*`、`/wallet/callback/`、`/logo/`
    以及 `/.well-known/` 下的原生应用关联文件。

    - Agent 访问：`/agents/*` 和 `/grants/`，包括用于实时检查 AI agent 的 access token 的 `GET
    /agents/session/`。Agent 访问不属于当前版本的约定范围。

    - Authentication 的控制台管理 API（该 API 接收控制台会话）。


    Authentication 事件目前尚未作为 Webhook 投递，因此本文档未定义任何 Webhook。
servers:
  - url: https://{issuer}
    description: 您的应用的签发方：其登录来源。
    variables:
      issuer:
        default: your-app.auth.didit.me
        description: >-
          签发方主机：`<name>.auth.didit.me`（生产环境）、`<name>.sandbox.auth.didit.me`（沙盒）或您当前生效的自定义网域。
security: []
tags:
  - name: Issuer
    description: 发现和签名密钥，用于核验 token。
  - name: Tokens
    description: 授权码授予和刷新授予。
  - name: Sessions
    description: 检查并结束访问 token 背后的会话。
  - name: Step-ups
    description: 敏感操作前的验证。
  - name: Monitoring
    description: 用于交易监控事件的设备凭据。
paths:
  /monitoring-token/:
    post:
      tags:
        - Monitoring
      summary: 创建监控 token
      description: >-
        将用户的 access token 兑换为短期设备凭证，以便用户的浏览器或应用无需通过您的后端即可将活动事件发送至交易监控。该凭证与用户的
        `vendor_data` 及请求的 `Origin` 绑定。


        浏览器及原生 SDK 会调用此 endpoint；服务端 SDK 则不会调用。在 Didit 启用该功能之前，该功能处于关闭状态，并会返回
        `503 monitoring_unavailable`。
      operationId: authentication_create_monitoring_token
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MonitoringTokenRequest'
            example: {}
      responses:
        '201':
          description: 针对该人员的设备凭证。
          headers:
            Cache-Control:
              description: 始终为 `no-store`：切勿缓存此响应。
              schema:
                type: string
                example: no-store
            Pragma:
              description: 始终为 `no-cache`。
              schema:
                type: string
                example: no-cache
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MonitoringToken'
              examples:
                MonitoringToken:
                  summary: 针对该人员浏览器的凭证
                  value:
                    monitoring_token: mt_2f8c1d9e7b6a5f4e3d2c1b0a
                    expires_at: '2026-10-17T10:56:14Z'
                    allowed_event_categories:
                      - session
                      - gameplay
                      - bonus
                      - affiliate
                      - payment
                      - dispute
                      - responsible_gambling
                      - custom
                    max_uses: 1000
                    bind_to_origin: https://app.example.com
                    vendor_data: user-42
        '400':
          description: 请求体包含字段。请发送一个空对象。
          headers:
            Cache-Control:
              description: 始终为 `no-store`：切勿缓存此响应。
              schema:
                type: string
                example: no-store
            Pragma:
              description: 始终为 `no-cache`。
              schema:
                type: string
                example: no-cache
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FieldErrors'
              examples:
                UnknownField:
                  summary: 请求体中的一个字段
                  value:
                    vendor_data: unknown_field
        '401':
          $ref: '#/components/responses/InvalidToken'
        '403':
          $ref: '#/components/responses/OriginNotAllowed'
        '404':
          $ref: '#/components/responses/ApplicationNotFound'
        '409':
          description: 该应用的事件监控已关闭。请在交易监控设置中开启事件采集。
          headers:
            Cache-Control:
              description: 始终为 `no-store`：切勿缓存此响应。
              schema:
                type: string
                example: no-store
            Pragma:
              description: 始终为 `no-cache`。
              schema:
                type: string
                example: no-cache
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
              examples:
                MonitoringDisabled:
                  summary: 事件采集已关闭
                  value:
                    error: monitoring_disabled
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          description: >-
            监控 token 未启用，或监控无法签发
            token（`monitoring_unavailable`），或请求所依赖的服务暂时不可用（`temporarily_unavailable`）。请稍后重试。
          headers:
            Cache-Control:
              description: 始终为 `no-store`：切勿缓存此响应。
              schema:
                type: string
                example: no-store
            Pragma:
              description: 始终为 `no-cache`。
              schema:
                type: string
                example: no-cache
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/OAuthError'
              examples:
                MonitoringUnavailable:
                  summary: 不可用
                  value:
                    error: monitoring_unavailable
                TemporarilyUnavailable:
                  summary: 稍后重试
                  value:
                    error: temporarily_unavailable
      security:
        - AccessToken: []
components:
  schemas:
    MonitoringTokenRequest:
      type: object
      description: 为空：访问 token 已指明该人员。
      additionalProperties: false
      properties: {}
    MonitoringToken:
      type: object
      description: 用于从用户设备向交易监控发送该人员活动事件的短期设备凭证。它不与会话绑定：在 `expires_at` 之前保持可用，即使退出登录后也是如此。
      required:
        - monitoring_token
        - expires_at
        - allowed_event_categories
        - max_uses
        - bind_to_origin
        - vendor_data
      properties:
        monitoring_token:
          type: string
          description: 设备凭证。请作为机密信息妥善保管。
          example: mt_2f8c1d9e7b6a5f4e3d2c1b0a
        expires_at:
          type: string
          format: date-time
          description: 签发后最多 15 分钟。
          example: '2026-10-17T10:56:14Z'
        allowed_event_categories:
          type: array
          items:
            type: string
          description: 其可能发送的事件类别。身份验证和安全事件始终来自 Didit，绝不来自设备。
          example:
            - session
            - gameplay
            - bonus
            - affiliate
            - payment
            - dispute
            - responsible_gambling
            - custom
        max_uses:
          type: integer
          example: 1000
        bind_to_origin:
          type: string
          nullable: true
          description: 凭证适用的浏览器来源：请求的 `Origin`，若无则为 `null`。
          example: https://app.example.com
        vendor_data:
          type: string
          description: 记录所有事件的人员，无论设备发送什么内容。
          example: user-42
    FieldErrors:
      title: Field errors
      type: object
      description: >-
        按字段划分的验证错误。值为消息列表或单个代码（`unknown_field`、`invalid_redirect_uri`）。`non_field_errors`
        包含跨字段规则错误。
      additionalProperties:
        oneOf:
          - type: string
          - type: array
            items:
              type: string
    OAuthError:
      title: OAuth error
      type: object
      description: OAuth 错误（RFC 6749 第 5.2 节）或服务错误。
      required:
        - error
      properties:
        error:
          type: string
          example: invalid_grant
    Detail:
      type: object
      required:
        - detail
      properties:
        detail:
          type: string
          example: invalid_token
  responses:
    InvalidToken:
      description: >-
        访问 token 缺失、格式错误、已过期、由其他签发方或密钥签名、为 ID token、为代理
        token，或已被刷新替换；或者其会话已被撤销、因闲置超时、已超过生命周期或不再符合您的 MFA
        策略，抑或是对应人员已被屏蔽或其邮箱不再处于已验证状态。请让该人员重新登录。
      headers:
        WWW-Authenticate:
          description: '`Bearer`。'
          schema:
            type: string
            example: Bearer
        Cache-Control:
          description: 始终为 `no-store`：切勿缓存此响应。
          schema:
            type: string
            example: no-store
        Pragma:
          description: 始终为 `no-cache`。
          schema:
            type: string
            example: no-cache
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Detail'
          examples:
            InvalidToken:
              summary: 已拒绝的访问令牌
              value:
                detail: invalid_token
    OriginNotAllowed:
      description: 请求携带的 `Origin` 既不是该应用自身的源，也不在其允许的源列表中。服务端请求不发送 `Origin`。
      headers:
        Cache-Control:
          description: 始终为 `no-store`：切勿缓存此响应。
          schema:
            type: string
            example: no-store
        Pragma:
          description: 始终为 `no-cache`。
          schema:
            type: string
            example: no-cache
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Detail'
          examples:
            OriginNotAllowed:
              summary: 来源（Origin）不允许
              value:
                detail: origin_not_allowed
    ApplicationNotFound:
      description: >-
        该地址属于某个应用，但该应用尚未启用 Authentication 功能；在 `apx.didit.me` 地址上，应用 ID
        未知。不属于任何应用的地址在到达 endpoint 之前即被拒绝，且该响应不是 JSON 格式。
      headers:
        Cache-Control:
          description: 始终为 `no-store`：切勿缓存此响应。
          schema:
            type: string
            example: no-store
        Pragma:
          description: 始终为 `no-cache`。
          schema:
            type: string
            example: no-cache
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Detail'
          examples:
            ApplicationNotFound:
              summary: 未知应用
              value:
                detail: No Configuration matches the given query.
    RateLimited:
      description: >-
        此地址针对该 endpoint 的请求过多，或者在 `POST` 或 `DELETE` 上机器人防护令牌桶已耗尽。未执行任何操作：请等待
        `Retry-After` 秒后重试。
      headers:
        Retry-After:
          description: 重试前需要等待的秒数。
          schema:
            type: integer
            example: 60
        Cache-Control:
          description: 始终为 `no-store`：切勿缓存此响应。
          schema:
            type: string
            example: no-store
        Pragma:
          description: 始终为 `no-cache`。
          schema:
            type: string
            example: no-cache
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Detail'
          examples:
            RateLimited:
              summary: endpoint 限制
              value:
                detail: >-
                  Too many requests from this IP address. Please wait before
                  trying again. Expected available in 60 seconds.
            BotProtection:
              summary: 机器人防护限制（POST 和 DELETE）
              value:
                detail: rate_limited Expected available in 2 seconds.
  securitySchemes:
    AccessToken:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: >-
        来自 `POST /token/` 的该人员访问 token：请求头带有 `typ: at+jwt` 的 ES256 JWT，有效期最多 5
        分钟。并非您的 Didit API 密钥。

````

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