openapi: 3.0.3
info:
  title: Baron SSO API
  version: "0.2.0"
  description: |
    Baron SSO Backend API 문서입니다.
    이 문서는 Swagger UI/ReDoc 단일 엔드포인트에서 확인할 수 있으며,
    도메인별 API 스펙은 점진적으로 확장합니다.
servers:
  - url: /
    description: 동일 호스트 기준

tags:
  - name: System
    description: 공통/헬스/문서
  - name: Auth
    description: 인증/세션/비밀번호/링크 로그인
  - name: Signup
    description: 회원가입/검증
  - name: User
    description: 사용자 프로필
  - name: Session
    description: 세션 관리(계획)
  - name: Admin
    description: 관리자 기능/테넌트
  - name: Public
    description: 인증 없이 외부에 노출되는 공개 API
  - name: Integrations
    description: API Key 기반 외부 연동 API
  - name: Dev
    description: RP/Consent 관리
  - name: Audit
    description: 감사 로그
  - name: Webhook
    description: 외부 게이트웨이 연동

paths:
  /.well-known/baron-rp-manifest:
    get:
      tags: [Public]
      summary: RP IAM Manifest HTML
      description: 외부 RP가 Baron SSO/IAM 연동 정책을 사람이 읽을 수 있는 HTML로 확인합니다.
      responses:
        "200":
          description: HTML
          content:
            text/html:
              schema:
                type: string

  /.well-known/baron-rp-manifest.json:
    get:
      tags: [Public]
      summary: RP IAM Manifest JSON
      description: 외부 RP가 Baron SSO/IAM 연동 정책을 machine-readable JSON으로 조회합니다.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RPManifestResponse"

  /.well-known/baron-rp-manifest.schema.json:
    get:
      tags: [Public]
      summary: RP IAM Manifest JSON Schema
      description: RP IAM Manifest JSON의 JSON Schema를 조회합니다.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true

  /health:
    get:
      tags: [System]
      summary: 헬스 체크
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"

  /openapi.yaml:
    get:
      tags: [System]
      summary: OpenAPI YAML
      responses:
        "200":
          description: OK
          content:
            application/yaml:
              schema:
                type: string

  /docs:
    get:
      tags: [System]
      summary: Swagger UI
      responses:
        "200":
          description: HTML
          content:
            text/html:
              schema:
                type: string

  /redoc:
    get:
      tags: [System]
      summary: ReDoc
      responses:
        "200":
          description: HTML
          content:
            text/html:
              schema:
                type: string

  /api/v1/public/orgchart:
    get:
      tags: [Public]
      summary: 공유 링크 조직도 조회
      description: 공유 링크 token으로 공개 가능한 조직도 데이터를 조회합니다.
      parameters:
        - in: query
          name: token
          required: true
          schema:
            type: string
          description: 조직도 공유 링크 token
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicOrgChartResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/tenants/registration:
    post:
      tags: [Public]
      summary: 테넌트 등록 요청
      description: 외부에서 테넌트 등록을 요청하고 승인 대기 상태로 생성합니다.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TenantRegistrationRequest"
      responses:
        "202":
          description: Accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TenantRegistrationResponse"
        "400":
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/integrations/org-context:
    get:
      tags: [Integrations]
      summary: 조직 Context 조회
      description: |
        외부 연동앱이 계정 세션 없이 API Key로 Baron SSO 조직 subtree를 조회합니다.
        `tenantSlug`가 없으면 `hanmac-family` 전체 subtree를 반환하며, slug 기반 지정만 계약으로 지원합니다.
      security:
        - BaronApiKeyId: []
          BaronApiKeySecret: []
      parameters:
        - in: query
          name: tenantSlug
          required: false
          schema:
            type: string
            default: hanmac-family
          description: 조회할 subtree root tenant slug
        - in: query
          name: includeUsers
          required: false
          schema:
            type: boolean
            default: true
          description: false이면 tenant members를 빈 배열로 반환합니다.
        - in: query
          name: includeUserIds
          required: false
          schema:
            type: boolean
            default: false
          description: true이면 tenant members[].id와 members[].phone만 추가합니다.
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OrgContextResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Tenant slug not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/auth/password/login:
    post:
      tags: [Auth]
      summary: 비밀번호 로그인
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PasswordLoginRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PasswordLoginResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/auth/password/reset/initiate:
    post:
      tags: [Auth]
      summary: 비밀번호 재설정 시작
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PasswordResetInitiateRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"

  /api/v1/auth/password/reset/verify:
    get:
      tags: [Auth]
      summary: 비밀번호 재설정 토큰 확인 페이지
      responses:
        "200":
          description: HTML
          content:
            text/html:
              schema:
                type: string
    post:
      tags: [Auth]
      summary: 비밀번호 재설정 토큰 처리
      requestBody:
        required: false
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              properties:
                token:
                  type: string
      responses:
        "302":
          description: Redirect

  /api/v1/auth/password/reset/complete:
    post:
      tags: [Auth]
      summary: 비밀번호 재설정 완료
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PasswordResetCompleteRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"

  /api/v1/auth/password/policy:
    get:
      tags: [Auth]
      summary: 비밀번호 정책 조회
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PasswordPolicyResponse"

  /api/v1/auth/enchanted-link/init:
    post:
      tags: [Auth]
      summary: Enchanted Link 시작
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EnchantedLinkInitRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EnchantedLinkInitResponse"

  /api/v1/auth/enchanted-link/poll:
    post:
      tags: [Auth]
      summary: Enchanted Link 폴링
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EnchantedLinkPollRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EnchantedLinkPollResponse"

  /api/v1/auth/magic-link/verify:
    post:
      tags: [Auth]
      summary: 매직 링크 검증
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MagicLinkVerifyRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MagicLinkVerifyResponse"

  /api/v1/auth/sms:
    post:
      tags: [Auth]
      summary: SMS 전송
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SmsSendRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"

  /api/v1/auth/verify-sms:
    post:
      tags: [Auth]
      summary: SMS 인증 코드 검증
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SmsVerifyRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SmsVerifyResponse"

  /api/v1/auth/qr/init:
    post:
      tags: [Auth]
      summary: QR 로그인 세션 생성
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QrInitResponse"

  /api/v1/auth/qr/poll:
    post:
      tags: [Auth]
      summary: QR 로그인 폴링
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/QrPollRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/QrPollResponse"

  /api/v1/auth/qr/approve:
    post:
      tags: [Auth]
      summary: QR 로그인 승인
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/QrApproveRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"

  /api/v1/auth/signup/check-email:
    post:
      tags: [Signup]
      summary: 회원가입 이메일 확인
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SignupEmailCheckRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"

  /api/v1/auth/signup/send-email-code:
    post:
      tags: [Signup]
      summary: 회원가입 이메일 코드 발송
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SignupEmailCodeRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"

  /api/v1/auth/signup/send-sms-code:
    post:
      tags: [Signup]
      summary: 회원가입 SMS 코드 발송
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SignupSmsCodeRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"

  /api/v1/auth/signup/verify-code:
    post:
      tags: [Signup]
      summary: 회원가입 코드 검증
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SignupVerifyCodeRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"

  /api/v1/auth/signup:
    post:
      tags: [Signup]
      summary: 회원가입 완료
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SignupRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SignupResponse"

  /api/v1/user/me:
    get:
      tags: [User]
      summary: 내 프로필 조회
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserProfileResponse"
    put:
      tags: [User]
      summary: 내 프로필 수정
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserProfileUpdateRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UserProfileResponse"

  /api/v1/user/me/send-code:
    post:
      tags: [User]
      summary: 프로필 변경 인증코드 전송
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserProfileSendCodeRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"

  /api/v1/user/me/password:
    post:
      tags: [User]
      summary: 비밀번호 변경
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserPasswordChangeRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"

  /api/v1/user/me/verify-code:
    post:
      tags: [User]
      summary: 프로필 변경 인증코드 검증
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UserProfileVerifyCodeRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"

  /api/v1/user/rp/linked:
    get:
      tags: [User]
      summary: 연동된 RP 목록 조회
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LinkedRpListResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/sessions:
    get:
      tags: [Session]
      summary: 세션 목록
      description: 세션 관리 API는 계획 단계입니다.
      x-status: planned
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionListResponse"

  /api/v1/sessions/{id}:
    get:
      tags: [Session]
      summary: 세션 상세
      description: 세션 관리 API는 계획 단계입니다.
      x-status: planned
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SessionDetailResponse"
    delete:
      tags: [Session]
      summary: 세션 로그아웃(폐기)
      description: 세션 관리 API는 계획 단계입니다.
      x-status: planned
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "204":
          description: No Content

  /api/v1/sessions/logout-all:
    post:
      tags: [Session]
      summary: 모든 세션 로그아웃
      description: 세션 관리 API는 계획 단계입니다.
      x-status: planned
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"

  /api/v1/admin/check:
    get:
      tags: [Admin]
      summary: 관리자 인증 확인
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/admin/tenants:
    get:
      tags: [Admin]
      summary: 테넌트 목록
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TenantListResponse"
    post:
      tags: [Admin]
      summary: 테넌트 생성
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TenantCreateRequest"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TenantResponse"

  /api/v1/admin/tenants/export:
    get:
      tags: [Admin]
      summary: 테넌트 CSV 내보내기
      description: |
        super admin 전용 CSV export입니다. `parentId`를 지정하면 해당 tenant의 모든 descendant를 leaf까지 포함해 내보냅니다.
      parameters:
        - in: query
          name: includeIds
          required: false
          schema:
            type: boolean
            default: false
          description: true이면 tenant_id와 parent_tenant_id 컬럼을 포함합니다.
        - in: query
          name: parentId
          required: false
          schema:
            type: string
          description: 하위 subtree를 내보낼 parent tenant ID
      responses:
        "200":
          description: CSV
          content:
            text/csv:
              schema:
                type: string
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/admin/tenants/{id}:
    get:
      tags: [Admin]
      summary: 테넌트 상세
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TenantResponse"
    put:
      tags: [Admin]
      summary: 테넌트 수정
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TenantUpdateRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TenantResponse"
    delete:
      tags: [Admin]
      summary: 테넌트 삭제
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "204":
          description: No Content

  /api/v1/admin/api-keys:
    get:
      tags: [Admin]
      summary: API Key 목록
      description: super admin 전용 API Key 관리 화면에서 사용합니다.
      parameters:
        - in: query
          name: limit
          required: false
          schema:
            type: integer
            default: 50
        - in: query
          name: offset
          required: false
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKeyListResponse"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    post:
      tags: [Admin]
      summary: API Key 생성
      description: super admin만 API Key를 생성할 수 있으며, clientSecret은 생성 응답에서 한 번만 반환됩니다.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ApiKeyCreateRequest"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKeyCreateResponse"
        "400":
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/admin/api-keys/{id}:
    patch:
      tags: [Admin]
      summary: API Key 권한 수정
      description: super admin 전용 API Key scope 수정입니다. client_id는 변경하지 않습니다.
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ApiKeyUpdateScopesRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKeySummary"
        "400":
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "404":
          description: Not Found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
    delete:
      tags: [Admin]
      summary: API Key 삭제
      description: super admin 전용 API Key 삭제입니다.
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "204":
          description: No Content
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/admin/api-keys/{id}/secret/rotate:
    post:
      tags: [Admin]
      summary: API Key Secret 재발급
      description: super admin 전용 Secret rotation입니다. client_id는 유지되며 clientSecret은 응답에서 한 번만 반환됩니다.
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKeyCreateResponse"
        "404":
          description: Not Found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"

  /api/v1/dev/clients:
    get:
      tags: [Dev]
      summary: RP 목록
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClientListResponse"
    post:
      tags: [Dev]
      summary: RP 생성
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ClientCreateRequest"
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClientDetailResponse"

  /api/v1/dev/clients/{id}:
    get:
      tags: [Dev]
      summary: RP 상세
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClientDetailResponse"
    put:
      tags: [Dev]
      summary: RP 수정
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ClientUpdateRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClientDetailResponse"
    delete:
      tags: [Dev]
      summary: RP 삭제
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        "204":
          description: No Content

  /api/v1/dev/clients/{id}/status:
    patch:
      tags: [Dev]
      summary: RP 상태 변경
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ClientStatusPatchRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClientDetailResponse"

  /api/v1/dev/consents:
    get:
      tags: [Dev]
      summary: Consent 목록
      parameters:
        - in: query
          name: subject
          required: true
          schema:
            type: string
        - in: query
          name: client_id
          required: false
          schema:
            type: string
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConsentListResponse"
    delete:
      tags: [Dev]
      summary: Consent 회수
      parameters:
        - in: query
          name: subject
          required: true
          schema:
            type: string
        - in: query
          name: client_id
          required: false
          schema:
            type: string
      responses:
        "204":
          description: No Content

  /api/v1/audit:
    post:
      tags: [Audit]
      summary: 감사 로그 수집
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AuditLogRequest"
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageResponse"

  /api/v1/client-log:
    post:
      tags: [Audit]
      summary: 클라이언트 로그 수집
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ClientLogRequest"
      responses:
        "200":
          description: OK

components:
  securitySchemes:
    BaronApiKeyId:
      type: apiKey
      in: header
      name: X-Baron-Key-ID
      description: |
        API Key client ID입니다. Swagger UI 우측 상단 `Authorize`에서 `X-Baron-Key-ID` 값으로 입력하면
        API Key 인증이 필요한 요청의 header에 자동으로 포함됩니다.
    BaronApiKeySecret:
      type: apiKey
      in: header
      name: X-Baron-Key-Secret
      description: |
        API Key client secret입니다. Swagger UI 우측 상단 `Authorize`에서 `X-Baron-Key-Secret` 값으로 입력하면
        API Key 인증이 필요한 요청의 header에 자동으로 포함됩니다.

  schemas:
    ErrorResponse:
      type: object
      properties:
        error:
          type: string
        code:
          type: string
        details:
          type: object
          additionalProperties: true

    MessageResponse:
      type: object
      properties:
        message:
          type: string
        status:
          type: string

    HealthResponse:
      type: object
      properties:
        status:
          type: string
        checks:
          type: object

    RPManifestResponse:
      type: object
      additionalProperties: true
      properties:
        version:
          type: string
          example: "2026-05-11"
        issuer:
          type: string
          example: https://sso.hmac.kr
        oidc:
          type: object
          additionalProperties: true
        iam:
          type: object
          additionalProperties: true
        identity_contract:
          type: object
          additionalProperties: true
        trusted_headers:
          type: object
          additionalProperties: true

    PublicOrgChartUser:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        position:
          type: string
        jobTitle:
          type: string
        companyCode:
          type: string
        status:
          type: string

    PublicOrgChartResponse:
      type: object
      properties:
        tenants:
          type: array
          items:
            $ref: "#/components/schemas/TenantResponse"
        users:
          type: array
          items:
            $ref: "#/components/schemas/PublicOrgChartUser"
        sharedWith:
          type: string

    TenantRegistrationRequest:
      type: object
      required: [name, domain, adminEmail]
      properties:
        name:
          type: string
        slug:
          type: string
        description:
          type: string
        domain:
          type: string
        adminEmail:
          type: string
          format: email

    TenantRegistrationResponse:
      type: object
      properties:
        message:
          type: string
        tenant:
          $ref: "#/components/schemas/TenantResponse"

    OrgContextScope:
      type: object
      properties:
        tenantId:
          type: string
          example: 01970f08-91da-7286-bd19-882fb98d1f2c
        tenantSlug:
          type: string
          example: hanmac-family

    OrgContextTenant:
      type: object
      properties:
        id:
          type: string
          example: 01970f08-91da-7286-bd19-882fb98d1f2c
        type:
          type: string
          example: COMPANY_GROUP
        name:
          type: string
          example: 한맥가족
        slug:
          type: string
          example: hanmac-family
        parentId:
          type: string
          nullable: true
        status:
          type: string
          example: active
        description:
          type: string
        domains:
          type: array
          items:
            type: string
        memberCount:
          type: integer
        visibility:
          type: string
          example: public
        orgUnitType:
          type: string
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time
        members:
          type: array
          description: 해당 tenant에 직접 소속된 사용자 목록입니다.
          items:
            $ref: "#/components/schemas/OrgContextMember"

    OrgContextTreeNode:
      allOf:
        - $ref: "#/components/schemas/OrgContextTenant"
        - type: object
          properties:
            children:
              type: array
              items:
                $ref: "#/components/schemas/OrgContextTreeNode"

    OrgContextMember:
      type: object
      properties:
        id:
          type: string
          description: includeUserIds=true일 때만 포함합니다.
        email:
          type: string
          format: email
        name:
          type: string
        phone:
          type: string
          description: includeUserIds=true일 때만 포함합니다.
        department:
          type: string
        grade:
          type: string
        position:
          type: string
        jobTitle:
          type: string
        isOwner:
          type: boolean
          description: appointment의 isOwner 또는 isManager 기준입니다.
        isLeader:
          type: boolean
          description: appointment의 lead 또는 isLead 기준이며, owner 계열 값이 true이면 true입니다.
        isPrimary:
          type: boolean
          description: appointment의 representative, isPrimary 또는 primary 기준입니다.

    OrgContextResponse:
      type: object
      properties:
        schemaVersion:
          type: string
          example: baron.org-context.v1
        issuedAt:
          type: string
          format: date-time
        scope:
          $ref: "#/components/schemas/OrgContextScope"
        tree:
          $ref: "#/components/schemas/OrgContextTreeNode"
        tenants:
          type: array
          items:
            $ref: "#/components/schemas/OrgContextTenant"

    PasswordLoginRequest:
      type: object
      required: [loginId, password]
      properties:
        loginId:
          type: string
        password:
          type: string

    PasswordLoginResponse:
      type: object
      properties:
        sessionJwt:
          type: string
        refreshJwt:
          type: string
        status:
          type: string
        provider:
          type: string
        subject:
          type: string

    PasswordResetInitiateRequest:
      type: object
      required: [loginId]
      properties:
        loginId:
          type: string

    PasswordResetCompleteRequest:
      type: object
      required: [newPassword]
      properties:
        newPassword:
          type: string

    PasswordPolicyResponse:
      type: object
      properties:
        minLength:
          type: integer
        lowercase:
          type: boolean
        uppercase:
          type: boolean
        number:
          type: boolean
        nonAlphanumeric:
          type: boolean
        minCharacterTypes:
          type: integer

    EnchantedLinkInitRequest:
      type: object
      properties:
        loginId:
          type: string
        uri:
          type: string
        method:
          type: string

    EnchantedLinkInitResponse:
      type: object
      properties:
        linkId:
          type: string
        pendingRef:
          type: string
        maskedEmail:
          type: string
        expiresIn:
          type: integer

    EnchantedLinkPollRequest:
      type: object
      properties:
        pendingRef:
          type: string

    EnchantedLinkPollResponse:
      type: object
      properties:
        status:
          type: string
        sessionJwt:
          type: string

    MagicLinkVerifyRequest:
      type: object
      properties:
        token:
          type: string
        verifyOnly:
          type: boolean

    MagicLinkVerifyResponse:
      type: object
      properties:
        token:
          type: string
        status:
          type: string
        pendingRef:
          type: string
        message:
          type: string

    SmsSendRequest:
      type: object
      properties:
        phoneNumber:
          type: string

    SmsVerifyRequest:
      type: object
      properties:
        phoneNumber:
          type: string
        code:
          type: string

    SmsVerifyResponse:
      type: object
      properties:
        token:
          type: string
        message:
          type: string

    QrInitResponse:
      type: object
      properties:
        qrCode:
          type: string
        pendingRef:
          type: string
        expiresIn:
          type: integer

    QrPollRequest:
      type: object
      properties:
        pendingRef:
          type: string

    QrPollResponse:
      type: object
      properties:
        status:
          type: string
        sessionJwt:
          type: string

    QrApproveRequest:
      type: object
      properties:
        pendingRef:
          type: string
        token:
          type: string

    SignupEmailCheckRequest:
      type: object
      properties:
        email:
          type: string

    SignupEmailCodeRequest:
      type: object
      properties:
        email:
          type: string

    SignupSmsCodeRequest:
      type: object
      properties:
        phone:
          type: string

    SignupVerifyCodeRequest:
      type: object
      properties:
        target:
          type: string
        code:
          type: string

    SignupRequest:
      type: object
      properties:
        email:
          type: string
        phone:
          type: string
        name:
          type: string
        password:
          type: string
        department:
          type: string
        affiliationType:
          type: string
        companyCode:
          type: string

    SignupResponse:
      type: object
      properties:
        success:
          type: boolean
        message:
          type: string
        provider:
          type: string
        subject:
          type: string

    UserProfileResponse:
      type: object
      properties:
        id:
          type: string
        email:
          type: string
        name:
          type: string
        phone:
          type: string
        sessionAuthenticatedAt:
          type: string
          format: date-time
        department:
          type: string
        affiliationType:
          type: string
        companyCode:
          type: string

    UserProfileUpdateRequest:
      type: object
      properties:
        name:
          type: string
        phone:
          type: string
        department:
          type: string
        affiliationType:
          type: string
        companyCode:
          type: string

    UserPasswordChangeRequest:
      type: object
      properties:
        currentPassword:
          type: string
        newPassword:
          type: string

    UserProfileSendCodeRequest:
      type: object
      properties:
        phone:
          type: string

    UserProfileVerifyCodeRequest:
      type: object
      properties:
        phone:
          type: string
        code:
          type: string

    LinkedRpSummary:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        logo:
          type: string
        url:
          type: string
        init_url:
          type: string
        auto_login_supported:
          type: boolean
        auto_login_url:
          type: string
        lastAuthenticatedAt:
          type: string
        status:
          type: string
        scopes:
          type: array
          items:
            type: string

    LinkedRpListResponse:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/LinkedRpSummary"

    SessionSummary:
      type: object
      properties:
        id:
          type: string
        issuedAt:
          type: string
        expiresAt:
          type: string
        device:
          type: string
        ip:
          type: string
        userAgent:
          type: string

    SessionListResponse:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/SessionSummary"

    SessionDetailResponse:
      type: object
      properties:
        session:
          $ref: "#/components/schemas/SessionSummary"

    TenantResponse:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        slug:
          type: string
        description:
          type: string
        status:
          type: string
        createdAt:
          type: string
        updatedAt:
          type: string

    TenantListResponse:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/TenantResponse"
        limit:
          type: integer
        offset:
          type: integer
        total:
          type: integer

    TenantCreateRequest:
      type: object
      required: [name]
      properties:
        name:
          type: string
        slug:
          type: string
        description:
          type: string
        status:
          type: string

    TenantUpdateRequest:
      type: object
      properties:
        name:
          type: string
        slug:
          type: string
        description:
          type: string
        status:
          type: string

    ApiKeySummary:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        client_id:
          type: string
        scopes:
          type: array
          items:
            type: string
          example: [org-context:read]
        status:
          type: string
          example: active
        lastUsedAt:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time

    ApiKeyListResponse:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/ApiKeySummary"
        total:
          type: integer

    ApiKeyCreateRequest:
      type: object
      required: [name]
      properties:
        name:
          type: string
        scopes:
          type: array
          items:
            type: string
          example: [org-context:read]

    ApiKeyUpdateScopesRequest:
      type: object
      required: [scopes]
      properties:
        scopes:
          type: array
          minItems: 1
          items:
            type: string
          example: [audit:read, org-context:read]

    ApiKeyCreateResponse:
      type: object
      properties:
        apiKey:
          $ref: "#/components/schemas/ApiKeySummary"
        clientSecret:
          type: string
          description: 생성 시점에 한 번만 반환되는 plain secret

    ClientSummary:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        type:
          type: string
        status:
          type: string
        createdAt:
          type: string
        redirectUris:
          type: array
          items:
            type: string
        scopes:
          type: array
          items:
            type: string
        metadata:
          type: object
          additionalProperties: true

    ClientListResponse:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/ClientSummary"
        limit:
          type: integer
        offset:
          type: integer

    ClientEndpoints:
      type: object
      properties:
        discovery:
          type: string
        issuer:
          type: string
        authorization:
          type: string
        token:
          type: string
        userinfo:
          type: string

    ClientDetailResponse:
      type: object
      properties:
        client:
          $ref: "#/components/schemas/ClientSummary"
        endpoints:
          $ref: "#/components/schemas/ClientEndpoints"

    ClientCreateRequest:
      type: object
      required: [redirectUris]
      properties:
        id:
          type: string
        name:
          type: string
        type:
          type: string
        status:
          type: string
        redirectUris:
          type: array
          items:
            type: string
        scopes:
          type: array
          items:
            type: string
        grantTypes:
          type: array
          items:
            type: string
        responseTypes:
          type: array
          items:
            type: string
        tokenEndpointAuthMethod:
          type: string
        metadata:
          type: object
          additionalProperties: true

    ClientUpdateRequest:
      type: object
      properties:
        name:
          type: string
        type:
          type: string
        status:
          type: string
        redirectUris:
          type: array
          items:
            type: string
        scopes:
          type: array
          items:
            type: string
        grantTypes:
          type: array
          items:
            type: string
        responseTypes:
          type: array
          items:
            type: string
        tokenEndpointAuthMethod:
          type: string
        metadata:
          type: object
          additionalProperties: true

    ClientStatusPatchRequest:
      type: object
      required: [status]
      properties:
        status:
          type: string

    ConsentSummary:
      type: object
      properties:
        subject:
          type: string
        clientId:
          type: string
        clientName:
          type: string
        grantedScopes:
          type: array
          items:
            type: string
        authenticatedAt:
          type: string

    ConsentListResponse:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/ConsentSummary"

    AuditLogRequest:
      type: object
      properties:
        service:
          type: string
        event:
          type: string
        payload:
          type: object
          additionalProperties: true

    ClientLogRequest:
      type: object
      properties:
        level:
          type: string
        message:
          type: string
        data:
          type: object
          additionalProperties: true
