openapi: 3.1.0
info:
  title: Quub Exchange - Identity Service
  version: 2.0.0
  license:
    name: Proprietary
    identifier: Proprietary
  description:
    "Core Identity and Access Management (IAM) layer for Quub Exchange.

    Handles account lifecycle, organization management, roles, and API key issuance.

    "
servers:
  - url: https://api.quub.exchange/v1
    description: Production
  - url: https://api.sandbox.quub.exchange/v1
    description: Sandbox
security:
  - bearerAuth: []
tags:
  - name: identity
    description: Identity, accounts, organizations, and access controls
  - name: Authentication
    description: Login, registration, and session management
paths:
  /Accounts:
    get:
      tags:
        - identity
      summary: List accounts
      operationId: listAccounts
      description: Retrieve paginated list of user and org accounts.
      parameters:
        - $ref: ./common/pagination.yaml#/components/parameters/cursor
        - $ref: ./common/pagination.yaml#/components/parameters/limit
        - $ref: ./common/components.yaml#/components/parameters/sort
        - name: status
          in: query
          schema:
            $ref: ./common/primitives.yaml#/components/schemas/AccountStatus
        - name: type
          in: query
          schema:
            $ref: ./common/primitives.yaml#/components/schemas/AccountType
        - name: orgId
          in: query
          required: false
          schema:
            $ref: ./common/primitives.yaml#/components/schemas/OrgId
          description: Filter accounts by organization
      responses:
        "200":
          description: List of accounts
          headers:
            Request-Id:
              $ref: ./common/components.yaml#/components/headers/Request-Id
          content:
            application/json:
              schema:
                allOf:
                  - $ref: ./common/pagination.yaml#/components/schemas/PageResponse
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: ./common/domain-models.yaml#/components/schemas/Account
        "400":
          $ref: ./common/responses.yaml#/components/responses/BadRequest
        "401":
          $ref: ./common/responses.yaml#/components/responses/Unauthorized
        "403":
          $ref: ./common/responses.yaml#/components/responses/Forbidden
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
    post:
      tags:
        - identity
      summary: Create a new account
      operationId: createAccount
      parameters:
        - $ref: ./common/components.yaml#/components/parameters/idempotencyKey
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - type
                - email
              properties:
                type:
                  $ref: ./common/primitives.yaml#/components/schemas/AccountType
                orgId:
                  $ref: ./common/primitives.yaml#/components/schemas/OrgId
                email:
                  type: string
                  format: email
                phone:
                  type: string
                  pattern: ^\+[1-9]\d{1,14}$
            example:
              type: INDIVIDUAL
              email: alice@example.com
              phone: "+14155551234"
      responses:
        "400":
          $ref: ./common/responses.yaml#/components/responses/BadRequest
        "401":
          $ref: ./common/responses.yaml#/components/responses/Unauthorized
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
        "201":
          description: Account created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: ./common/domain-models.yaml#/components/schemas/Account
        "403":
          $ref: ./common/responses.yaml#/components/responses/Forbidden
  /Accounts/{accountId}:
    get:
      tags:
        - identity
      summary: Retrieve account by ID
      operationId: getAccount
      parameters:
        - name: accountId
          in: path
          required: true
          schema:
            $ref: ./common/primitives.yaml#/components/schemas/AccountId
      responses:
        "400":
          $ref: ./common/responses.yaml#/components/responses/BadRequest
        "401":
          $ref: ./common/responses.yaml#/components/responses/Unauthorized
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
        "200":
          description: Account details
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: ./common/domain-models.yaml#/components/schemas/Account
        "403":
          $ref: ./common/responses.yaml#/components/responses/Forbidden
    patch:
      tags:
        - identity
      summary: Update an existing account
      operationId: updateAccount
      parameters:
        - name: accountId
          in: path
          required: true
          schema:
            $ref: ./common/primitives.yaml#/components/schemas/AccountId
        - $ref: ./common/components.yaml#/components/parameters/idempotencyKey
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  format: email
                phone:
                  type: string
                  pattern: ^\+[1-9]\d{1,14}$
                status:
                  $ref: ./common/primitives.yaml#/components/schemas/AccountStatus
      responses:
        "200":
          description: Account updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: ./common/domain-models.yaml#/components/schemas/Account
        "400":
          $ref: ./common/responses.yaml#/components/responses/BadRequest
        "401":
          $ref: ./common/responses.yaml#/components/responses/Unauthorized
        "403":
          $ref: ./common/responses.yaml#/components/responses/Forbidden
        "404":
          $ref: ./common/responses.yaml#/components/responses/NotFound
        "422":
          $ref: ./common/responses.yaml#/components/responses/ValidationError
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
  /Orgs:
    get:
      tags:
        - identity
      summary: List organizations
      operationId: listOrgs
      parameters:
        - $ref: ./common/pagination.yaml#/components/parameters/cursor
        - $ref: ./common/pagination.yaml#/components/parameters/limit
        - name: country
          in: query
          schema:
            type: string
            pattern: ^[A-Z]{2}$
      responses:
        "200":
          description: List of organizations
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    type: array
                    items:
                      $ref: ./common/domain-models.yaml#/components/schemas/Org
                  meta:
                    $ref: ./common/pagination.yaml#/components/schemas/PageMeta
        "400":
          $ref: ./common/responses.yaml#/components/responses/BadRequest
        "401":
          $ref: ./common/responses.yaml#/components/responses/Unauthorized
        "403":
          $ref: ./common/responses.yaml#/components/responses/Forbidden
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
    post:
      tags:
        - identity
      summary: Create organization
      operationId: createOrg
      parameters:
        - $ref: ./common/components.yaml#/components/parameters/idempotencyKey
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - legalName
                - country
              properties:
                legalName:
                  type: string
                regNo:
                  type: string
                country:
                  type: string
                  pattern: ^[A-Z]{2}$
                roles:
                  type: array
                  items:
                    type: string
      responses:
        "201":
          description: Organization created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Org"
        "400":
          $ref: ./common/responses.yaml#/components/responses/BadRequest
        "401":
          $ref: ./common/responses.yaml#/components/responses/Unauthorized
        "403":
          $ref: ./common/responses.yaml#/components/responses/Forbidden
        "409":
          $ref: ./common/responses.yaml#/components/responses/Conflict
        "422":
          $ref: ./common/responses.yaml#/components/responses/ValidationError
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
  /Orgs/{orgId}:
    get:
      tags:
        - identity
      summary: Retrieve organization by ID
      operationId: getOrg
      parameters:
        - $ref: ./common/components.yaml#/components/parameters/orgId
        - $ref: ./common/components.yaml#/components/parameters/orgIdHeader
      responses:
        "400":
          $ref: ./common/responses.yaml#/components/responses/BadRequest
        "401":
          $ref: ./common/responses.yaml#/components/responses/Unauthorized
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
        "200":
          description: Organization details
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Org"
        "403":
          $ref: ./common/responses.yaml#/components/responses/Forbidden
    patch:
      tags:
        - identity
      summary: Update organization
      operationId: updateOrg
      parameters:
        - $ref: ./common/components.yaml#/components/parameters/orgId
        - $ref: ./common/components.yaml#/components/parameters/orgIdHeader
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                legalName:
                  type: string
                roles:
                  type: array
                  items:
                    type: string
      responses:
        "200":
          description: Organization updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Org"
        "400":
          $ref: ./common/responses.yaml#/components/responses/BadRequest
        "401":
          $ref: ./common/responses.yaml#/components/responses/Unauthorized
        "403":
          $ref: ./common/responses.yaml#/components/responses/Forbidden
        "404":
          $ref: ./common/responses.yaml#/components/responses/NotFound
        "422":
          $ref: ./common/responses.yaml#/components/responses/ValidationError
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
  /Roles:
    get:
      tags:
        - identity
      summary: List available roles
      operationId: listRoles
      responses:
        "200":
          description: List of roles
          content:
            application/json:
              schema:
                allOf:
                  - $ref: ./common/pagination.yaml#/components/schemas/PageResponse
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: "#/components/schemas/Role"
                      meta:
                        $ref: ./common/pagination.yaml#/components/schemas/PageMeta
        "400":
          $ref: ./common/responses.yaml#/components/responses/BadRequest
        "401":
          $ref: ./common/responses.yaml#/components/responses/Unauthorized
        "403":
          $ref: ./common/responses.yaml#/components/responses/Forbidden
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
  /ApiKeys:
    get:
      tags:
        - identity
      summary: List API keys for account
      operationId: listApiKeys
      parameters:
        - name: accountId
          in: query
          required: true
          schema:
            $ref: ./common/primitives.yaml#/components/schemas/AccountId
      responses:
        "200":
          description: List of API keys (masked)
          content:
            application/json:
              schema:
                allOf:
                  - $ref: ./common/pagination.yaml#/components/schemas/PageResponse
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: ./common/domain-models.yaml#/components/schemas/ApiKey
                      meta:
                        $ref: ./common/pagination.yaml#/components/schemas/PageMeta
        "400":
          $ref: ./common/responses.yaml#/components/responses/BadRequest
        "401":
          $ref: ./common/responses.yaml#/components/responses/Unauthorized
        "403":
          $ref: ./common/responses.yaml#/components/responses/Forbidden
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
    post:
      tags:
        - identity
      summary: Create new API key
      operationId: createApiKey
      description:
        "The full secret key is returned only once — store it securely.

        "
      parameters:
        - $ref: ./common/components.yaml#/components/parameters/idempotencyKey
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - accountId
                - scopes
              properties:
                accountId:
                  $ref: ./common/primitives.yaml#/components/schemas/AccountId
                scopes:
                  type: array
                  items:
                    type: string
      responses:
        "201":
          description: API key created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    allOf:
                      - $ref: ./common/domain-models.yaml#/components/schemas/ApiKey
                      - type: object
                        properties:
                          secretKey:
                            type: string
                            description: Secret key (visible once)
                            example: "your_secret_key_here"
        "400":
          $ref: ./common/responses.yaml#/components/responses/BadRequest
        "401":
          $ref: ./common/responses.yaml#/components/responses/Unauthorized
        "403":
          $ref: ./common/responses.yaml#/components/responses/Forbidden
        "409":
          $ref: ./common/responses.yaml#/components/responses/Conflict
        "422":
          $ref: ./common/responses.yaml#/components/responses/ValidationError
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
  /ApiKeys/{apiKeyId}:
    delete:
      tags:
        - identity
      summary: Revoke API key
      operationId: deleteApiKey
      parameters:
        - name: apiKeyId
          in: path
          required: true
          schema:
            type: string
            pattern: ^apik_[a-zA-Z0-9]{16,32}$
      responses:
        "204":
          description: API key revoked successfully
        "400":
          $ref: ./common/responses.yaml#/components/responses/BadRequest
        "401":
          $ref: ./common/responses.yaml#/components/responses/Unauthorized
        "403":
          $ref: ./common/responses.yaml#/components/responses/Forbidden
        "404":
          $ref: ./common/responses.yaml#/components/responses/NotFound
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
  /Accounts/{accountId}/mfa:
    post:
      tags:
        - identity
      summary: Enable MFA for account
      operationId: enableMfa
      description:
        "Enable multi-factor authentication. Returns QR code and secret for TOTP setup.

        "
      parameters:
        - name: accountId
          in: path
          required: true
          schema:
            $ref: ./common/primitives.yaml#/components/schemas/AccountId
        - $ref: ./common/components.yaml#/components/parameters/idempotencyKey
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - method
              properties:
                method:
                  type: string
                  enum:
                    - TOTP
                    - SMS
                    - EMAIL
                  description: MFA method to enable
                phoneNumber:
                  type: string
                  pattern: ^\+[1-9]\d{1,14}$
                  description: Required if method is SMS
      responses:
        "400":
          $ref: ./common/responses.yaml#/components/responses/BadRequest
        "401":
          $ref: ./common/responses.yaml#/components/responses/Unauthorized
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
        "201":
          description: MFA enabled successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/MfaSetup"
        "403":
          $ref: ./common/responses.yaml#/components/responses/Forbidden
    delete:
      tags:
        - identity
      summary: Disable MFA for account
      operationId: disableMfa
      parameters:
        - name: accountId
          in: path
          required: true
          schema:
            $ref: ./common/primitives.yaml#/components/schemas/AccountId
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - code
              properties:
                code:
                  type: string
                  description: Current MFA code to confirm disable
      responses:
        "204":
          description: MFA disabled successfully
        "400":
          $ref: ./common/responses.yaml#/components/responses/BadRequest
        "401":
          $ref: ./common/responses.yaml#/components/responses/Unauthorized
        "403":
          $ref: ./common/responses.yaml#/components/responses/Forbidden
        "404":
          $ref: ./common/responses.yaml#/components/responses/NotFound
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
  /Accounts/{accountId}/mfa/verify:
    post:
      tags:
        - identity
      summary: Verify MFA setup
      operationId: verifyMfaSetup
      description:
        "Verify TOTP code during initial MFA setup. Activates MFA after successful verification.

        "
      parameters:
        - name: accountId
          in: path
          required: true
          schema:
            $ref: ./common/primitives.yaml#/components/schemas/AccountId
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - code
              properties:
                code:
                  type: string
                  description: TOTP code from authenticator app
                  example: "123456"
      responses:
        "200":
          description: MFA verified and activated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      backupCodes:
                        type: array
                        items:
                          type: string
                        description: One-time backup codes (shown once)
                        example:
                          - a1b2c3d4
                          - e5f6g7h8
        "400":
          $ref: ./common/responses.yaml#/components/responses/BadRequest
        "401":
          $ref: ./common/responses.yaml#/components/responses/Unauthorized
        "403":
          $ref: ./common/responses.yaml#/components/responses/Forbidden
        "422":
          $ref: ./common/responses.yaml#/components/responses/ValidationError
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
  /Auth/mfa/challenge:
    post:
      tags:
        - identity
      summary: Complete MFA challenge
      operationId: completeMfaChallenge
      description:
        "Complete MFA challenge after successful password authentication.

        Returns access token on success.

        "
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - challengeId
                - code
              properties:
                challengeId:
                  type: string
                  description: Challenge ID from login response
                code:
                  type: string
                  description: TOTP code or backup code
                  example: "123456"
      responses:
        "200":
          description: MFA challenge completed
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    required:
                      - accessToken
                      - expiresIn
                    properties:
                      accessToken:
                        type: string
                        description: JWT access token after successful MFA
                        example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
                      expiresIn:
                        type: integer
                        description: Token expiration time in seconds (RFC 6749)
                        example: 3600
                      tokenType:
                        type: string
                        description: Token type per OAuth2 spec
                        enum: [Bearer]
                        default: Bearer
        "400":
          $ref: ./common/responses.yaml#/components/responses/BadRequest
        "401":
          $ref: ./common/responses.yaml#/components/responses/Unauthorized
        "403":
          $ref: ./common/responses.yaml#/components/responses/Forbidden
        "422":
          $ref: ./common/responses.yaml#/components/responses/ValidationError
        "409":
          $ref: ./common/responses.yaml#/components/responses/Conflict
        "429":
          $ref: ./common/responses.yaml#/components/responses/TooManyRequests
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
  /auth/login:
    post:
      tags:
        - Authentication
      summary: Login with email and password
      operationId: login
      security: [] # No auth required for login
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - email
                - password
              properties:
                email:
                  type: string
                  format: email
                password:
                  type: string
                  format: password
      responses:
        "200":
          description: Login successful
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    required:
                      - accessToken
                      - refreshToken
                      - expiresIn
                    properties:
                      accessToken:
                        type: string
                        description: JWT access token
                        example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
                      refreshToken:
                        type: string
                        description: Refresh token for obtaining new access tokens
                        example: "refresh_abc123def456..."
                      expiresIn:
                        type: integer
                        description: Token expiration time in seconds (RFC 6749)
                        example: 3600
                      tokenType:
                        type: string
                        description: Token type per OAuth2 spec
                        enum: [Bearer]
                        default: Bearer
        "202":
          description: MFA required - complete challenge to obtain tokens
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/MfaChallenge"
        "400":
          $ref: ./common/responses.yaml#/components/responses/BadRequest
        "401":
          $ref: ./common/responses.yaml#/components/responses/Unauthorized
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
  /auth/register:
    post:
      tags:
        - Authentication
      summary: Register a new account
      operationId: register
      security: [] # No auth required for registration
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - email
                - password
              properties:
                email:
                  type: string
                  format: email
                password:
                  type: string
                  format: password
                  minLength: 8
                firstName:
                  type: string
                lastName:
                  type: string
      responses:
        "201":
          description: Registration successful
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    required:
                      - accessToken
                      - expiresIn
                      - accountId
                    properties:
                      accessToken:
                        type: string
                        description: JWT access token
                        example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
                      expiresIn:
                        type: integer
                        description: Token expiration time in seconds (RFC 6749)
                        example: 3600
                      tokenType:
                        type: string
                        description: Token type per OAuth2 spec
                        enum: [Bearer]
                        default: Bearer
                      accountId:
                        $ref: ./common/primitives.yaml#/components/schemas/AccountId
                        description: ID of the newly created account
        "400":
          $ref: ./common/responses.yaml#/components/responses/BadRequest
        "409":
          $ref: ./common/responses.yaml#/components/responses/Conflict
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
  /auth/logout:
    post:
      tags:
        - Authentication
      summary: Logout and invalidate session
      operationId: logout
      responses:
        "204":
          description: Logout successful
        "401":
          $ref: ./common/responses.yaml#/components/responses/Unauthorized
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
  /auth/me:
    get:
      tags:
        - Authentication
      summary: Get current authenticated user
      operationId: getCurrentUser
      responses:
        "200":
          description: Current user retrieved
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: ./common/domain-models.yaml#/components/schemas/Account
        "401":
          $ref: ./common/responses.yaml#/components/responses/Unauthorized
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
  /auth/change-password:
    post:
      tags:
        - Authentication
      summary: Change account password
      operationId: changePassword
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - currentPassword
                - newPassword
              properties:
                currentPassword:
                  type: string
                  format: password
                newPassword:
                  type: string
                  format: password
                  minLength: 8
      responses:
        "204":
          description: Password changed successfully
        "400":
          $ref: ./common/responses.yaml#/components/responses/BadRequest
        "401":
          $ref: ./common/responses.yaml#/components/responses/Unauthorized
        "500":
          $ref: ./common/responses.yaml#/components/responses/InternalServerError
components:
  schemas:
    Role:
      type: object
      description: Account role within an organization
      required:
        - id
        - accountId
        - orgId
        - role
      properties:
        id:
          type: string
          format: uuid
        accountId:
          $ref: ./common/primitives.yaml#/components/schemas/AccountId
        orgId:
          $ref: ./common/primitives.yaml#/components/schemas/OrgId
        role:
          type: string
          enum:
            - ADMIN
            - OPERATIONS
            - COMPLIANCE
            - FINANCE
            - PROJECT_MANAGER
            - INVESTOR
        permissions:
          type: array
          items:
            type: string
          description: Granular permissions assigned to this role
        assignedAt:
          type: string
          format: date-time
        assignedBy:
          type: string
          description: Account ID of assigner
    Org:
      type: object
      description: Simplified organization reference (alias for Organization from domain-models)
      required:
        - id
        - legalName
        - status
      properties:
        id:
          $ref: ./common/primitives.yaml#/components/schemas/OrgId
        legalName:
          type: string
        status:
          type: string
          enum:
            - ACTIVE
            - SUSPENDED
            - CLOSED
        type:
          type: string
          enum:
            - ISSUER
            - SPV
            - CUSTODIAN
            - INVESTOR
        country:
          type: string
          example: US
        createdAt:
          type: string
          format: date-time
    MfaSetup:
      type: object
      description: MFA setup response with TOTP configuration
      required:
        - method
        - status
      properties:
        method:
          type: string
          enum:
            - TOTP
            - SMS
            - EMAIL
        status:
          type: string
          enum:
            - PENDING_VERIFICATION
            - ACTIVE
        totpSecret:
          type: string
          description: Base32-encoded TOTP secret (only for TOTP method)
          example: JBSWY3DPEHPK3PXP
        qrCodeUri:
          type: string
          description: otpauth:// URI for QR code generation
          example: otpauth://totp/QuubExchange:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=QuubExchange
        phoneNumber:
          type: string
          description: Masked phone number (only for SMS method)
          example: +1***555**34
    MfaChallenge:
      type: object
      description: MFA challenge issued after password login
      required:
        - challengeId
        - methods
        - expiresAt
      properties:
        challengeId:
          type: string
          format: uuid
          description: Unique challenge identifier
        methods:
          type: array
          items:
            type: string
            enum:
              - TOTP
              - SMS
              - EMAIL
              - BACKUP_CODE
          description: Available MFA methods for this account
        expiresAt:
          type: string
          format: date-time
          description: Challenge expiration timestamp
  securitySchemes:
    bearerAuth:
      $ref: ./common/components.yaml#/components/securitySchemes/bearerAuth
    apiKey:
      $ref: ./common/components.yaml#/components/securitySchemes/apiKey
    oauth2:
      $ref: ./common/components.yaml#/components/securitySchemes/oauth2
