> ## 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: 헤드리스 인증 토큰

> Didit의 POST /programmatic/login/은 2FA 없이 액세스 토큰을 직접 반환하는 헤드리스 인증 API입니다. 에이전트 네이티브, MCP 지원, 사용량 기반 요금제.



프로그래밍 방식 로그인 프롬프트
        프롬프트를 클립보드에 복사



      
      

\<pre>

\`Exchange a verified Didit account's email + password for a fresh JWT access token (headless, no browser).

Endpoint:
POST https://apx.didit.me/auth/v2/programmatic/login/

Authentication:
None. Email + password is the credential.

Request body:
\- email (string, required) — Same email used at /programmatic/register/.
\- password (string, required) — Plain password (HTTPS only).

Account requirements:
The account must have a verified email and password. Accounts created through POST /programmatic/register/ work immediately after email verification. Console-created email/password accounts can also use this endpoint. OAuth-only console accounts must set a password first.

curl example:
curl -X POST https://apx.didit.me/auth/v2/programmatic/login/ \
-H "Content-Type: application/json" \
-d '{
"email": "you@yourdomain.com",
"password": "MyStr0ng!Pass"
}'

Response (200 OK):
\- access_token (string) — RS256 JWT. Pass as "Authorization: Bearer <access_token>" on every Account Management endpoint at apx.didit.me/auth/v2. DO NOT use against verification.didit.me — that host uses x-api-key.
\- refresh_token (string) — RS256 JWT for refresh flows.
\- expires_in (integer) — Default 86400 (24 hours).

Token shape:
\- 24-hour default lifetime.
\- RS256-signed.
\- No browser session is required.

Brute-force protection:
\- Progressive account lockout per email: 5 consecutive failures = 15 min lockout, 10 = 1 hour, 20 = 24 hours.
\- Per-IP rate limit: 20 attempts/minute and 100 attempts/hour.

Failure modes:
\- 400 — { "email": ["..."] } / { "password": ["..."] } field-level errors.
\- 401 — { "detail": "Invalid email or password." }
\- 403 — { "detail": "..." } when SSO policy blocks password login for the account.
\- 429 — { "detail": "Too many login attempts.", "wait": <seconds> } on lockout or per-IP limit. Honour the wait value before retrying.

Recovering your api_key:
After a successful login, call GET /organizations/me/ to enumerate organizations and applications, then GET /organizations/me/{org_id}/applications/{app_id}/ to read the api_key — see /auth-api/get-credentials.

Where to use access_token vs api_key:
\- access_token (Bearer) → https://apx.didit.me/auth/v2/... (org / app management).
\- api_key (x-api-key) → https://verification.didit.me/v3/... (sessions, workflows, billing).

For end-to-end Didit integration, paste in the full prompt at /integration/integration-prompt.`

\</pre>



      
      

붙여넣을 곳



## OpenAPI

````yaml ko/openapi-auth.json POST /programmatic/login/
openapi: 3.0.0
info:
  version: 1.0.0
  title: Didit Auth API
  description: >-
    개발자와 AI 에이전트를 위한 프로그래밍 방식 등록 및 인증 API입니다. 브라우저 없이 API 호출 2번만으로 등록할 수 있습니다.
    에이전트 친화성이 가장 뛰어난 신원 인증 플랫폼입니다.


    ## 기본 URL


    이 사양의 모든 endpoint는 `https://apx.didit.me/auth/v2`에서 호스팅됩니다. 이는 인증
    API(`https://verification.didit.me/v3`)와는 다른 호스트입니다. 혼동하지 않도록 유의하십시오. 이 인증
    호스트는 **JWT 액세스 토큰**을 발급하며 **조직** 및 **애플리케이션**을 나열하고 관리할 수 있도록 합니다. 인증 호스트는 이
    API가 반환한 **`api_key`**로 인증하여 세션, 워크플로, AML 등을 호출하는 곳입니다.


    ## 인증 흐름 작동 방식


    1. `POST /programmatic/register/` — 이메일 + 비밀번호 제출.

    2. 이메일로 6자리 영숫자 코드 수신.

    3. `POST /programmatic/verify-email/` — 코드 제출. JWT `access_token`,
    `refresh_token`, 조직, 그리고 `client_id` + `api_key`가 포함된 기본 애플리케이션을 반환받습니다.

    4. `api_key`를 저장합니다. `https://verification.didit.me/v3/...`에 대한 모든 호출에
    `x-api-key`로 사용하십시오.

    5. 향후 다른 머신에서 `POST /programmatic/login/`으로 이메일 + 비밀번호를 새 JWT로 교환한 다음, `GET
    /organizations/me/` 및 애플리케이션 endpoint를 호출하여 자격 증명을 복구하거나 교체할 수 있습니다.


    JWT는 이 사양의 **계정 관리** endpoint에만 필요합니다. 인증 API 트래픽은 JWT가 아닌 장기 유효 `api_key`를
    사용합니다.


    ## 규칙


    - 모든 요청 및 응답 본문은 JSON(`Content-Type: application/json`)입니다.

    - **`POST /programmatic/register/`를 테스트할 때는 실제로 수신 가능한 이메일 주소를 사용하십시오.**
    endpoint가 인증 코드를 동기식으로 전송하므로 예약된 테스트 도메인(`@example.com`, `@example.org`,
    `@*.test`, `@*.example`, `@*.invalid`)의 주소는 메일 발송이 거부되어 요청에서 `500`이 반환됩니다.
    샘플의 `you@yourdomain.com` 플레이스홀더를 실제로 확인할 수 있는 수신함 주소로 대체하십시오.

    - 유효성 검사 오류는 두 가지 엔벨로프 형식 중 하나와 함께 HTTP `400`을 반환합니다. 필드 수준 문제의 경우
    `{"field": ["message", ...]}` 형식이고, 비즈니스 규칙 오류의 경우 `{"detail": "message"}`
    형식입니다. 소수의 검증기(등록 시 비밀번호 강도, 이메일 인증 시 코드 만료)는 현재 필드 키 형식 대신 기본 배열
    `["message"]`로 응답합니다. 이는 서버 측의 알려진 불일치 사항이므로 클라이언트 코드가 두 형태를 모두 수용하도록 작성해야
    합니다.

    - 인증 오류는 HTTP `401`(유효하지 않은 Bearer 토큰) 또는 `403`(Bearer 토큰 누락 / 권한 부족)과 함께
    `{"detail": "..."}` 형태로 반환됩니다.

    - 속도 제한 응답에는 대기 시간을 초 단위로 나타내는 `wait` 필드가 포함됩니다.
servers:
  - url: https://apx.didit.me/auth/v2
    description: 프로덕션 인증 서버
security: []
tags:
  - name: Programmatic Auth
    description: >-
      이메일 및 비밀번호 계정 라이프사이클: 가입, 인증, 로그인. 계정 관리 endpoint에 사용할 JWT `access_token`과
      (인증 완료 시) 첫 번째 애플리케이션의 `client_id` 및 `api_key`를 반환합니다.
  - name: Account Management
    description: >-
      조직 목록을 조회하고 애플리케이션을 생성, 조회 또는 업데이트하는 인증된 endpoint(JWT bearer)입니다. 검증 API
      호출에 인증하는 데 사용하는 `api_key`를 복구하거나 교체할 때 사용하십시오.
paths:
  /programmatic/login/:
    post:
      tags:
        - Programmatic Auth
      summary: 프로그래밍 방식 로그인
      description: >-
        인증된 이메일/비밀번호 Didit 계정을 인증하고 RS256 JWT 쌍(기본 수명 86400초)을 받습니다. 콘솔에서 생성된
        이메일/비밀번호 계정은 이 endpoint를 사용할 수 있습니다. OAuth 전용 콘솔 계정은 먼저 비밀번호를 설정해야 합니다.
      operationId: programmatic_login
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
            examples:
              Default:
                summary: 프로그래밍 방식 로그인
                value:
                  email: you@yourdomain.com
                  password: MyStr0ng!Pass
      responses:
        '200':
          description: >-
            로그인 성공. 최신 JWT 쌍을 반환합니다. `access_token`을 `Authorization: Bearer
            <token>`으로 사용하여 `/organizations/me/...`에 대해 `org_id`, `app_id` 및
            `api_key`를 조회할 수 있습니다. 계정에 인증 앱이나 패스키가 등록되어 있는 경우, 응답에 토큰 대신
            `2fa_required: true` 및 `temp_token`이 포함됩니다.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
              examples:
                Success:
                  value:
                    access_token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
                    refresh_token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
                    expires_in: 86400
                    message: Login successful
        '400':
          description: 유효성 검사 오류입니다. 요청 본문의 필드 수준 문제입니다.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              examples:
                MissingFields:
                  summary: 빈 본문
                  value:
                    email:
                      - This field is required.
                    password:
                      - This field is required.
                InvalidEmail:
                  summary: 잘못된 형식의 이메일
                  value:
                    email:
                      - Enter a valid email address.
        '401':
          description: 인증이 거부되었습니다.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuthError'
              examples:
                InvalidCredentials:
                  summary: >-
                    이메일과 비밀번호가 일치하지 않습니다(사용자 열거 공격을 방지하기 위해 계정이 존재하지 않는 경우에도
                    반환됨).
                  value:
                    detail: Invalid email or password.
                NotVerified:
                  summary: 계정이 존재하지만 이메일이 아직 인증되지 않았습니다
                  value:
                    detail: Please verify your email address before logging in.
                SSORequired:
                  summary: 계정이 SSO를 요구하는 조직에 속해 있습니다.
                  value:
                    detail:
                      error: sso_required
                      message: Your organization 'Acme Corp' requires SSO login.
                      organization_name: Acme Corp
                      organization_slug: acme-corp
                      sso_url: /sso/acme-corp
        '403':
          description: >-
            계정이 복구되었으며 로그인하기 전에 새 로그인 방법을 설정해야 합니다. 설정을 위한 링크가 계정 주소로 이메일
            전송되었습니다. 조직에서 SSO를 적용하는 계정은 `error: sso_required`,
            `organization_name`, `organization_slug` 및 `sso_url`과 함께 `403`을
            받습니다.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecoverySetupRequiredError'
              examples:
                RecoverySetupRequired:
                  summary: 아직 로그인 방법이 없는 복구된 계정
                  value:
                    recovery_setup_required: true
                    setup_link_sent: true
                    message: >-
                      You recovered your account. Open the link we emailed you
                      to set up a new sign-in method.
                    code: recovery_setup_required
        '429':
          description: >-
            계정이 잠겼거나(이 이메일에 대한 최근 실패 횟수 초과) IP 주소에 속도 제한이 적용되었습니다(이 주소로부터의 시도 횟수
            초과). 또한 서비스가 한 번에 확인할 수 있는 최대치의 비밀번호 또는 코드를 일시적으로 검사 중일 때
            `Retry-After: 5` 헤더와 함께 반환됩니다. 표시된 시간(초)이 지난 후 다시 시도하십시오.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RateLimitError'
              examples:
                AccountLocked:
                  summary: 점진적 잠금 발동됨
                  value:
                    detail: >-
                      Account temporarily locked due to too many failed login
                      attempts. Please try again later.
                IPRateLimited:
                  summary: 해당 IP에서 분당 20회 또는 시간당 100회 초과
                  value:
                    detail: >-
                      Too many login attempts from this IP address. Please wait
                      before trying again.
                PasswordChecksBusy:
                  summary: 진행 중인 비밀번호 확인이 너무 많습니다
                  value:
                    detail: >-
                      Too many sign-in attempts are being processed right now.
                      Please try again in a few seconds.
      security:
        - {}
      x-codeSamples:
        - lang: curl
          label: cURL
          source: |-
            curl -X POST https://apx.didit.me/auth/v2/programmatic/login/ \
              -H "Content-Type: application/json" \
              -d '{
                "email": "you@yourdomain.com",
                "password": "MyStr0ng!Pass"
              }'
        - lang: python
          label: Python
          source: |-
            from didit import Didit

            # This call needs no API key.
            client = Didit()

            result = client.account.login(
                email="you@yourdomain.com",
                password="MyStr0ng!Pass",
            )
            print(result)
        - lang: javascript
          label: Node.js
          source: |-
            import Didit from "@didit-protocol/sdk-node";

            // This call needs no API key.
            const didit = new Didit();

            const result = await didit.account.login({
              email: "you@yourdomain.com",
              password: "MyStr0ng!Pass",
            });
            console.log(result);
        - lang: go
          label: Go
          source: "ctx := context.Background()\n// This call needs no API key.\nclient := didit.NewClient()\n\nresult, err := client.Account.Login(ctx, didit.LoginRequest{\n\tEmail:    \"you@yourdomain.com\",\n\tPassword: \"MyStr0ng!Pass\",\n})\nif err != nil {\n\tlog.Fatal(err)\n}\nfmt.Printf(\"%+v\\n\", result)"
        - lang: java
          label: Java
          source: |-
            import me.didit.Didit;
            import me.didit.types.LoginRequest;

            // This call needs no API key.
            Didit client = new Didit();

            var result = client.account().login(LoginRequest.builder()
                    .email("you@yourdomain.com")
                    .password("MyStr0ng!Pass")
                    .build());
            System.out.println(result);
        - lang: php
          label: PHP
          source: |-
            <?php

            require 'vendor/autoload.php';

            use Didit\DiditClient;

            // This call needs no API key.
            $didit = new DiditClient();

            $result = $didit->account->login([
                'email' => 'you@yourdomain.com',
                'password' => 'MyStr0ng!Pass',
            ]);
            print_r($result->toArray());
        - lang: ruby
          label: Ruby
          source: |-
            require "didit"

            # This call needs no API key.
            didit = Didit::Client.new

            result = didit.account.login(
              email: "you@yourdomain.com",
              password: "MyStr0ng!Pass"
            )
            puts result.to_h
        - lang: csharp
          label: .NET
          source: |-
            using System;
            using System.Text.Json;
            using Didit;

            // This call needs no API key.
            using var client = new DiditClient();

            var result = await client.Account.LoginAsync(new LoginRequest
            {
                Email = "you@yourdomain.com",
                Password = "MyStr0ng!Pass",
            });
            Console.WriteLine(JsonSerializer.Serialize(result));
components:
  schemas:
    LoginRequest:
      type: object
      required:
        - email
        - password
      properties:
        email:
          type: string
          format: email
          example: you@yourdomain.com
        password:
          type: string
          example: MyStr0ng!Pass
    LoginResponse:
      type: object
      properties:
        access_token:
          type: string
          description: >-
            RS256 서명 JWT입니다. 계정 관리 endpoint에 대해 `Authorization: Bearer
            <token>`으로 사용하십시오.
        refresh_token:
          type: string
        expires_in:
          type: integer
          description: 액세스 token의 유효 기간(초 단위, 기본값 86400)입니다.
          example: 86400
        message:
          type: string
          example: Login successful
    ValidationError:
      description: >-
        검증 오류 본문. 의도된 엔벨로프는 필드 수준 오류의 경우 `{field: ["message", ...]}` 맵이거나 비즈니스
        규칙 오류의 경우 `{detail: "..."}` 단일 키 엔벨로프입니다. 소수의 검증기(회원가입 시 비밀번호 강도,
        verify-email 시 코드 유효성)는 현재 단순 배열 `["message"]`로 응답합니다(알려진 서버 측 불일치). 아래
        스키마는 이 사양에서 생성된 클라이언트 코드가 현재 응답을 역직렬화할 수 있도록 해당 단순 배열 분기를 유지하지만, 새로운
        클라이언트 코드는 이를 관련 필드 키 형식(`{password: [...]}` 또는 `{code: [...]}`)으로 처리해야
        하며 향후 서버 규약도 해당 형태로 통일될 예정입니다.
      oneOf:
        - type: object
          additionalProperties:
            type: array
            items:
              type: string
          example:
            email:
              - Enter a valid email address.
        - type: object
          properties:
            detail:
              type: string
          example:
            detail: An account with this email already exists.
        - type: array
          items:
            type: string
          example:
            - Invalid or expired verification code.
    AuthError:
      type: object
      description: 인증 / 권한 부여 / 찾을 수 없음 오류 엔벨로프입니다.
      properties:
        detail:
          type: string
          example: Invalid access token
    RecoverySetupRequiredError:
      type: object
      description: >-
        계정이 복구되었으며 아직 로그인 방법이 설정되지 않았습니다. 토큰은 발급되지 않으며, 새 로그인 방법을 설정할 수 있는 일회용
        링크가 계정 주소로 이메일 전송되었습니다. 링크를 열어 방법을 설정한 후 다시 로그인하십시오.
      required:
        - recovery_setup_required
        - setup_link_sent
        - message
        - code
      properties:
        recovery_setup_required:
          type: boolean
          example: true
        setup_link_sent:
          type: boolean
          example: true
        message:
          type: string
          example: >-
            You recovered your account. Open the link we emailed you to set up a
            new sign-in method.
        code:
          type: string
          enum:
            - recovery_setup_required
          example: recovery_setup_required
    RateLimitError:
      type: object
      description: 속도 제한 또는 점진적 잠금 응답입니다. `wait`는 초 단위의 대기 시간입니다.
      properties:
        detail:
          type: string
          example: >-
            Too many login attempts from this IP address. Please wait before
            trying again.

````

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