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

# 모니터링 토큰 생성 | Didit Authentication API

> Didit의 POST /monitoring-token/은 액세스 토큰을 사용자의 vendor_data 및 브라우저 출처에 바인딩된 15분 기기 자격 증명으로 교환합니다.

> [!WARNING]
>
> 인증 기능은 새로 출시되었으며, Didit이 활성화하기 전까지 이 엔드포인트는 꺼진 상태로 유지됩니다. 그 전까지는 `503 monitoring_unavailable`로 응답합니다.

브라우저 및 네이티브 SDK는 사용자의 기기에서 이 엔드포인트를 호출하며, 서버에서 호출할 필요는 없습니다. 자격 증명과 함께 전송된 모든 이벤트는 기기가 무엇을 주장하든 관계없이 사용자의 `vendor_data`에 기록됩니다.


## OpenAPI

````yaml ko/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가 이를 가리키며, 공개 가능 키(publishable key)가 변경됩니다. 기본 호스트에서 발급된 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/`은 자격 증명을 요구하지 않습니다. 클라이언트는 공개 클라이언트(public
    client)입니다. 클라이언트 시크릿(client secret)이 없으며, 인가 코드(authorization code)는 PKCE
    검증자(`S256`)에 바인딩됩니다. `client_id`는 애플리케이션 ID입니다.

    - `/session/`, `/step-up/`, `/monitoring-token/`은 사용자의 access token을 사용합니다:
    `Authorization: Bearer <access_token>`. 성공한 각 호출은 세션의 활동으로 간주되므로 세션의 유휴 시간
    초과(idle timeout)가 재시작됩니다.


    ## 요청 본문


    본문을 받는 모든 endpoint는 JSON, 양식 인코딩(form-encoded) 및 multipart 본문을 허용합니다. 이 문서에는
    JSON과 OAuth 클라이언트가 전송하는 `POST /token/`용 양식 인코딩이 나열되어 있습니다. endpoint가 허용하지 않는
    필드는 `400 {"<field>": "unknown_field"}`를 반환합니다.


    ## Token


    - **Access token**: 헤더 `typ: at+jwt`를 가진 ES256 JWT로, 최대 5분 동안 유효하며 해당 세션보다
    오래 유효할 수 없습니다. 클레임: `AccessTokenClaims`.

    - **ID token**: 헤더 `typ: JWT`를 가진 ES256 JWT입니다. 클레임: `IDTokenClaims`. access
    token으로는 절대 허용되지 않습니다.

    - **Refresh token**: 사용할 때마다 순환(rotate)되는 임의의 문자열입니다. 이미 사용된 refresh token을
    다시 보내면 세션이 취소(revoke)됩니다.


    `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`). UUID가 아닌 `step_up_id`는 `404` 페이지를
    반환합니다.


    ## 속도 제한


    요청은 두 계층을 통과해야 합니다.


    클라이언트 IP 주소당, 애플리케이션당, endpoint당 계산되는 **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`.


    모든 `POST` 및 `DELETE`에 적용되는 **봇 보호**. 해당 버킷은 호스팅된 로그인 단계를 포함한 애플리케이션의 모든
    `POST` 및 `DELETE` 요청이 공유하며 지속적으로 리필됩니다:


    - 클라이언트 IP 주소당 분당 60건의 요청.

    - 브라우저 및 네이티브 SDK와 같이 기기를 식별하는 요청의 경우 기기당 분당 30건의 요청.

    - 모든 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` 헤더를 포함하는 요청은 애플리케이션 자체의 origin 또는 허용된 origin 중 하나에서 시작되어야 하며, 그렇지
    않으면 `403 origin_not_allowed`를 받습니다. 서버 간(server-to-server) 요청은 `Origin`을
    전송하지 않습니다.


    ## 여기에 포함되지 않는 내용


    이러한 경로는 호스팅 페이지, 브라우저 및 네이티브 SDK, AI 에이전트를 위한 경로입니다. 서버 규격(contract)에 포함되지
    않으며 서버 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/` 하위의 네이티브 앱 연결 파일.

    - 에이전트 액세스: `/agents/*` 및 `/grants/`(AI 에이전트의 access token 실시간 확인을 위한 `GET
    /agents/session/` 포함). 에이전트 액세스는 본 버전의 규격에 포함되지 않습니다.

    - 콘솔 세션을 사용하는 Authentication 콘솔 관리 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: 모니터링 토큰 생성
      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: '비어 있음: access 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: >-
        액세스 토큰이 누락되었거나, 형식이 잘못되었거나, 만료되었거나, 다른 발급자 또는 키로 서명되었거나, ID 토큰이거나, 에이전트
        토큰이거나, 토큰 갱신으로 교체되었습니다. 또는 세션이 취소되었거나 유휴 시간 초과로 만료되었거나 유효 수명이 지났거나 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: 거부된 액세스 token
              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: >-
        주소가 애플리케이션에 속해 있지만 해당 애플리케이션에 인증이 활성화되어 있지 않습니다. `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에 대해 해당 IP 주소로부터의 요청이 너무 많거나, `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.