openapi: 3.0.3
info:
  title: TKeeper API
  version: 2.3.0
  description: |
    TKeeper public HTTP API.
    JSON request bodies reject unknown properties and are limited to 10 MiB by the HTTP server.

servers:
  - url: /

tags:
  - name: System
  - name: Keys
  - name: DKG
  - name: Signing
  - name: TrustedDealer
  - name: Quorum
  - name: ECIES
  - name: Audit
  - name: Inventory
  - name: ControlPlane
  - name: Consistency
  - name: Expiration
  - name: Integrity

security:
  - DevToken: []
  - JwtToken: []

paths:
  /v1/keeper/system/status:
    get:
      tags: [System]
      operationId: getSystemStatus
      summary: Get keeper status
      responses:
        '200':
          description: Keeper status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StatusResponse'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/system/init:
    post:
      tags: [System]
      operationId: initializeKeeper
      summary: Initialize keeper
      description: |
        Writes keeper identity and quorum settings into the sealed store.
        `threshold=1,total=1` initializes mono mode. `threshold>1` initializes threshold/MPC mode.
      x-permissions:
        required: [tkeeper.system.init]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/KeeperInitData'
      responses:
        '200':
          description: Provider initialization data
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShamirInitData'
        '204':
          description: Initialized without provider payload
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/system/health:
    get:
      tags: [System]
      operationId: health
      summary: Liveness probe
      security: []
      responses:
        '204':
          description: Service is up

  /v1/keeper/system/ready:
    get:
      tags: [System]
      operationId: ready
      summary: Readiness probe
      security: []
      responses:
        '204':
          description: Keeper is unsealed
        '503':
          description: Keeper is not ready

  /v1/keeper/system/unseal:
    post:
      tags: [System]
      operationId: submitUnsealShares
      summary: Submit unseal shares
      x-permissions:
        required: [tkeeper.system.unseal]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Unseal'
      responses:
        '200':
          description: Unseal progress
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Progress'
        default:
          $ref: '#/components/responses/ErrorResponse'
    get:
      tags: [System]
      operationId: autoUnseal
      summary: Unseal with the configured automatic seal provider
      x-permissions:
        required: [tkeeper.system.unseal]
      responses:
        '200':
          description: Unseal progress
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Progress'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/system/seal:
    post:
      tags: [System]
      operationId: sealKeeper
      summary: Seal keeper
      x-permissions:
        required: [tkeeper.system.seal]
      responses:
        '204':
          description: Keeper sealed
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/ping:
    get:
      tags: [System]
      operationId: ping
      summary: Lightweight readiness status
      security: []
      responses:
        '200':
          description: Readiness flag
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShortStatus'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/peerId:
    get:
      tags: [System]
      operationId: peerId
      summary: Get local peer id
      responses:
        '200':
          description: Peer id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IntValue'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/publicKey:
    get:
      tags: [Keys]
      operationId: publicKey
      summary: Get public key
      x-permissions:
        template: tkeeper.key.{keyId}.public
      parameters:
        - $ref: '#/components/parameters/KeyIdQuery'
        - name: generation
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/Generation'
        - name: tweak
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/Tweak'
      responses:
        '200':
          description: Public key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicKeyDto'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v2/keeper/dkg:
    post:
      tags: [DKG]
      operationId: generateKey
      summary: Create, rotate, or refresh a key generation
      description: |
        Runs the key lifecycle operation in the current quorum mode.
        Mono mode manages full key material locally. Threshold mode runs distributed key generation across peers.
      x-permissions:
        byField:
          field: mode
          CREATE: tkeeper.dkg.create
          ROTATE: tkeeper.dkg.rotate
          REFRESH: tkeeper.dkg.refresh
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Generate'
      responses:
        '204':
          description: Lifecycle operation completed
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/destroy:
    post:
      tags: [Keys]
      operationId: destroyKeyGeneration
      summary: Destroy one key generation
      description: |
        Destroys a historical key generation.
        Mono mode is local-only and allows any non-current generation.
        Threshold mode coordinates across peers and requires the generation to be at least two generations behind the active one.
      x-permissions:
        template: tkeeper.key.{keyId}.destroy
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/KeyDestroyReference'
      responses:
        '204':
          description: Generation destroyed
        '299':
          description: Threshold destroy committed by quorum, but not all peers
          headers:
            Warning:
              schema:
                type: string
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v2/keeper/storage/store:
    post:
      tags: [TrustedDealer]
      operationId: trustedDealerStore
      summary: Import key material through trusted dealer flow
      description: |
        Imports existing private key material into the current quorum mode.
        Mono mode stores the key locally. Threshold mode splits the key into peer shares and stores commitments.
      x-permissions:
        required: [tkeeper.storage.write]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Store'
      responses:
        '200':
          description: Key material stored
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v2/keeper/quorum/promote:
    post:
      tags: [Quorum]
      operationId: promoteQuorum
      summary: Promote mono keeper into threshold quorum
      description: |
        Promotes a mono keeper into a threshold quorum.
        Target peers must already be initialized, unsealed, and configured with the requested threshold and total.
        The promoted keeper becomes peer 1 and must be restarted before normal operations continue.
      x-permissions:
        required: [tkeeper.quorum.promote]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/QuorumPromotion'
      responses:
        '200':
          description: Promotion completed; restart the keeper before normal operations
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QuorumPromotionResult'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v2/keeper/sign:
    post:
      tags: [Signing]
      operationId: sign
      summary: Sign in the current quorum mode
      description: |
        Materializes the command, evaluates key controls, and signs using the current quorum mode.
        Mono mode signs locally. Threshold mode uses FROST or GG20.
      x-permissions:
        template: tkeeper.key.{keyId}.sign
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Sign'
      responses:
        '200':
          description: Signature result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CalculatedSignature'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v2/keeper/sign/verify:
    post:
      tags: [Signing]
      operationId: verifySignature
      summary: Verify signature
      x-permissions:
        template: tkeeper.key.{keyId}.verify
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Verify'
      responses:
        '200':
          description: Verification result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerifyResult'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/ecies/encrypt:
    post:
      tags: [ECIES]
      operationId: eciesEncrypt
      summary: Encrypt with ECIES public key
      description: Encryption uses the public key and does not require peer participation.
      x-permissions:
        template: tkeeper.key.{keyId}.encrypt
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Encrypt'
      responses:
        '200':
          description: Ciphertext
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Encrypted'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/ecies/decrypt:
    post:
      tags: [ECIES]
      operationId: eciesDecrypt
      summary: Decrypt ECIES ciphertext
      description: |
        Decrypts in the current quorum mode.
        Mono mode decrypts locally. Threshold mode collects peer partial decrypts and verifies DLEQ proofs.
      x-permissions:
        template: tkeeper.key.{keyId}.decrypt
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Decrypt'
      responses:
        '200':
          description: Plaintext
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Decrypted'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/compliance/inventory:
    get:
      tags: [Inventory]
      operationId: assetInventory
      summary: List asset inventory
      x-permissions:
        required: [tkeeper.compliance.inventory]
      parameters:
        - name: logicalId
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/KeyId'
        - name: historical
          in: query
          required: false
          schema:
            type: boolean
            default: false
        - name: lastSeen
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/Cursor'
        - name: assetOwner
          in: query
          required: false
          schema:
            $ref: '#/components/schemas/AssetOwner'
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            format: int32
            default: 200
            minimum: 1
            maximum: 200
      responses:
        '200':
          description: Asset inventory page
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AssetInventoryPage'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/audit/verify:
    post:
      tags: [Audit]
      operationId: verifyAuditLine
      summary: Verify one signed audit line
      x-permissions:
        required: [tkeeper.audit.log.verify]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SignedLine'
      responses:
        '200':
          description: Verification result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerifyResult'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/audit/verify/batch:
    post:
      tags: [Audit]
      operationId: verifyAuditBatch
      summary: Verify a batch of signed audit lines
      x-permissions:
        required: [tkeeper.audit.log.verify]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Logs'
      responses:
        '200':
          description: Verification results by event id
          content:
            application/json:
              schema:
                type: object
                additionalProperties:
                  $ref: '#/components/schemas/VerifyResult'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/integrity/rotate:
    post:
      tags: [Integrity]
      operationId: rotateIntegrityKey
      summary: Rotate audit integrity key
      description: |
        Rotates this peer's audit and internal-response integrity key.
        Restart every peer after rotation so process-local peer key pins are re-enrolled.
      x-permissions:
        required: [tkeeper.integrity.rotate]
      responses:
        '204':
          description: Integrity key rotated
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/consistency/fix:
    post:
      tags: [Consistency]
      operationId: consistencyFix
      summary: Try to repair key consistency
      x-permissions:
        required: [tkeeper.consistency.fix]
      parameters:
        - $ref: '#/components/parameters/KeyIdQuery'
      responses:
        '200':
          description: Consistency verdict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConsistencyCheck'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/expires:
    get:
      tags: [Expiration]
      operationId: listExpires
      summary: List expiring key policy entries
      description: |
        Supply either a positive `windowSec`, or `to` with optional `from`.
        `windowSec` cannot be combined with `from` or `to`, and `from` cannot exceed `to`.
      x-permissions:
        required: [tkeeper.expired.view]
      parameters:
        - $ref: '#/components/parameters/ExpireTypeQuery'
        - $ref: '#/components/parameters/LimitQuery'
        - $ref: '#/components/parameters/CursorQuery'
        - name: windowSec
          in: query
          required: false
          schema:
            type: integer
            format: int64
            minimum: 1
        - name: from
          in: query
          required: false
          schema:
            type: integer
            format: int64
            minimum: 0
        - name: to
          in: query
          required: false
          schema:
            type: integer
            format: int64
            minimum: 0
      responses:
        '200':
          description: Expiration page
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExpirePage'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/expires/apply:
    get:
      tags: [Expiration]
      operationId: listApplyExpires
      summary: List keys with apply policy expiring in a window
      x-permissions:
        required: [tkeeper.expired.view]
      parameters:
        - $ref: '#/components/parameters/LimitQuery'
        - $ref: '#/components/parameters/CursorQuery'
        - name: windowSec
          in: query
          required: true
          schema:
            type: integer
            format: int64
            minimum: 1
      responses:
        '200':
          description: Expiration page
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExpirePage'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/expires/process:
    get:
      tags: [Expiration]
      operationId: listProcessExpires
      summary: List keys with process policy expiring in a window
      x-permissions:
        required: [tkeeper.expired.view]
      parameters:
        - $ref: '#/components/parameters/LimitQuery'
        - $ref: '#/components/parameters/CursorQuery'
        - name: windowSec
          in: query
          required: true
          schema:
            type: integer
            format: int64
            minimum: 1
      responses:
        '200':
          description: Expiration page
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExpirePage'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/expires/expired:
    get:
      tags: [Expiration]
      operationId: listExpired
      summary: List expired key policy entries
      x-permissions:
        required: [tkeeper.expired.view]
      parameters:
        - $ref: '#/components/parameters/ExpireTypeQuery'
        - $ref: '#/components/parameters/LimitQuery'
        - $ref: '#/components/parameters/CursorQuery'
      responses:
        '200':
          description: Expiration page
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ExpirePage'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/control/auth/config:
    get:
      tags: [ControlPlane]
      operationId: controlPlaneAuthConfig
      summary: Get UI auth config
      security: []
      responses:
        '200':
          description: UI auth config
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ControlPlaneAuthConfig'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/control/system:
    get:
      tags: [ControlPlane]
      operationId: controlPlaneSystem
      summary: Get cluster status for UI
      x-permissions:
        required: [tkeeper.control.system]
      responses:
        '200':
          description: Cluster status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SystemInfo'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/control/capabilities:
    get:
      tags: [ControlPlane]
      operationId: controlPlaneCapabilities
      summary: Get capabilities exposed by installed cryptographic platforms
      responses:
        '200':
          description: Runtime control-plane capabilities
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ControlPlaneCapabilities'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/control/audit/sinks:
    get:
      tags: [ControlPlane]
      operationId: controlPlaneAuditSinks
      summary: Get audit sink status
      x-permissions:
        required: [tkeeper.control.sinks]
      responses:
        '200':
          description: Audit sink status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AuditInfo'
        default:
          $ref: '#/components/responses/ErrorResponse'

  /v1/keeper/control/me:
    get:
      tags: [ControlPlane]
      operationId: controlPlaneMe
      summary: Get authenticated subject
      responses:
        '200':
          description: Authenticated subject
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SimpleAuthData'
        default:
          $ref: '#/components/responses/ErrorResponse'

components:
  securitySchemes:
    DevToken:
      type: apiKey
      in: header
      name: X-DEV-TOKEN
    JwtToken:
      type: apiKey
      in: header
      name: X-JWT-TOKEN

  parameters:
    KeyIdQuery:
      name: keyId
      in: query
      required: true
      schema:
        $ref: '#/components/schemas/KeyId'
    ExpireTypeQuery:
      name: type
      in: query
      required: true
      schema:
        type: string
        enum: [apply, process]
    LimitQuery:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        format: int32
        default: 100
        minimum: 1
        maximum: 2000
    CursorQuery:
      name: cursor
      in: query
      required: false
      schema:
        $ref: '#/components/schemas/Cursor'

  responses:
    ErrorResponse:
      description: Error response
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorMessage'

  schemas:
    ErrorMessage:
      type: object
      required: [error, imposters, dead]
      properties:
        error:
          $ref: '#/components/schemas/ErrorType'
        details:
          type: string
          nullable: true
        imposters:
          type: array
          items:
            type: string
        dead:
          type: array
          items:
            type: string
        approvals:
          type: array
          nullable: true
          items:
            $ref: '#/components/schemas/RequiredApprovalDetails'

    ErrorType:
      type: string
      enum:
        - KEEPER_ALREADY_INITIALIZED
        - KEEPER_ALREADY_UNSEALED
        - NOT_SUPPORTED_FOR_AUTO_PROVIDER
        - NOT_SUPPORTED_FOR_MANUAL_PROVIDER
        - KEEPER_NOT_INITIALIZED
        - KEEPER_SEALED
        - RESTART_REQUIRED
        - KEY_NOT_FOUND
        - SESSION_MANAGER_NOT_FOUND
        - SESSION_NOT_FOUND
        - UNSUPPORTED_KEY_ALGORITHM
        - SIGNATURE_VERIFIER_NOT_FOUND
        - SESSION_ALREADY_EXISTS
        - NOT_ENOUGH_HEALTHY_CLIENTS
        - NOT_ENOUGH_CONFIGURED_CLIENTS
        - NOT_ENOUGH_HONEST_CLIENTS
        - INVALID_CONFIG
        - INVALID_SIGNATURE
        - KEY_ALGORITHM_MISMATCH
        - INVALID_BASE64
        - INVALID_THRESHOLD
        - INVALID_TOTAL
        - INVALID_PEER_ID
        - INVALID_CIPHERTEXT
        - INVALID_SESSION_ID
        - INVALID_REQUEST_BODY
        - INVALID_QUERY_PARAMETER
        - INVALID_SHARE
        - INVALID_KEY_SET_POLICY
        - INVALID_KEY_GENERATION
        - INVALID_CURSOR
        - INVALID_EXPIRATION_TIME
        - INVALID_HASH_METHOD
        - INVALID_PAYLOAD
        - INVALID_SIG_SCHEME
        - INVALID_KEY_ID
        - INVALID_ASSET_OWNER
        - INVALID_EXPIRE_TYPE
        - INVALID_TWEAK
        - INVALID_AUTHORITY_ARTIFACT
        - INVALID_AUTHORITY_TYPE
        - INVALID_AUTHORITY
        - INVALID_INTENT
        - NOT_PARTICIPANT
        - SHARE_NOT_FOUND
        - IMPOSTER_FOUND
        - CORRUPTED_KEY_STATE
        - KEY_ALREADY_EXISTS
        - KEY_APPLY_OPS_FORBIDDEN
        - KEY_PROCESS_OPS_FORBIDDEN
        - AUDIT_FAILED
        - AUDIT_NOT_AVAILABLE
        - INTEGRITY_KEY_NOT_FOUND
        - HMAC_KEY_NOT_FOUND
        - MISSING_KEY_ID
        - MISSING_SESSION_ID
        - MISSING_KEY_VERSION
        - MISSING_WINDOW
        - MISSING_EXPIRE_TYPE
        - IDENTIFIABLE_ABORT
        - DEV_MODE_VIOLATION
        - APPROVAL_REQUIRED
        - ACCESS_DENIED
        - UNAUTHENTICATED
        - DESTROY_FORBIDDEN
        - POLICY_VIOLATION
        - AUTHORITY_VIOLATION
        - NOT_THRESHOLD
        - NOT_MONO
        - WARNING
        - CONSISTENCY_CHECK_FAILED
        - ROTATE_NEEDED
        - INCONSISTENT_KEEPER
        - TAMPERED_KEEPER
        - SESSION_MAX_ROUNDS_EXCEEDED
        - NOT_COORDINATOR
        - HSM_ERROR
        - INTERNAL_ERROR

    KeeperInitData:
      type: object
      additionalProperties: false
      description: |
        Keeper init data. `threshold=1,total=1` selects mono mode.
        `threshold>1,total>=threshold` selects threshold/MPC mode.
      required: [peerId, threshold, total]
      properties:
        peerId:
          type: integer
          format: int32
          minimum: 1
          maximum: 255
          description: Local peer id. Must not exceed `total`. Mono mode requires `1`.
        threshold:
          type: integer
          format: int32
          minimum: 1
          maximum: 255
          description: Required quorum size. Use `1` only with `total=1`.
        total:
          type: integer
          format: int32
          minimum: 1
          maximum: 255
          description: Total peers in the quorum. Must be `1` for mono mode and cannot exceed 255.

    ShamirInitData:
      type: object
      required: [threshold, total, shares64]
      properties:
        threshold:
          type: integer
          format: int32
        total:
          type: integer
          format: int32
        shares64:
          type: array
          minItems: 1
          maxItems: 255
          items:
            $ref: '#/components/schemas/NonEmptyBase64'

    Unseal:
      type: object
      additionalProperties: false
      anyOf:
        - required: [payload64]
        - required: [payloads64]
      properties:
        payload64:
          $ref: '#/components/schemas/NonEmptyBase64'
        payloads64:
          type: array
          minItems: 1
          maxItems: 255
          nullable: true
          items:
            $ref: '#/components/schemas/NonEmptyBase64'
        reset:
          type: boolean
          default: false
          nullable: true

    StoreState:
      type: string
      enum: [UNINITIALIZED, SEALED, UNSEALED]

    Progress:
      oneOf:
        - $ref: '#/components/schemas/ReadyProgress'
        - $ref: '#/components/schemas/ShamirUnsealProgress'

    ReadyProgress:
      type: object
      required: [ready]
      properties:
        ready:
          type: boolean

    ShamirUnsealProgress:
      type: object
      required: [threshold, total, progress, ready]
      properties:
        threshold:
          type: integer
          format: int32
        total:
          type: integer
          format: int32
        progress:
          type: integer
          format: int32
        ready:
          type: boolean

    StatusResponse:
      type: object
      required: [state, progress]
      properties:
        sealedBy:
          type: string
          nullable: true
        state:
          $ref: '#/components/schemas/StoreState'
        progress:
          $ref: '#/components/schemas/Progress'

    ShortStatus:
      type: object
      required: [ready]
      properties:
        ready:
          type: boolean
        peerId:
          type: integer
          format: int32
          nullable: true
        threshold:
          type: integer
          format: int32
          nullable: true
        total:
          type: integer
          format: int32
          nullable: true

    IntValue:
      type: object
      required: [serviceId]
      properties:
        serviceId:
          type: integer
          format: int32
        result:
          type: integer
          format: int32
          nullable: true

    PublicKeyDto:
      type: object
      required: [data64]
      properties:
        data64:
          $ref: '#/components/schemas/NonEmptyBase64'

    KeyId:
      type: string
      minLength: 1
      maxLength: 255
      pattern: ^[a-z0-9]+(?:-[a-z0-9]+)*(?::[a-z0-9]+(?:-[a-z0-9]+)*)?$

    AssetOwner:
      type: string
      minLength: 1
      maxLength: 255
      pattern: ^[a-z0-9]+(?:[-_:/][a-z0-9]+)*$

    AuthorityId:
      type: string
      minLength: 1
      maxLength: 255
      pattern: ^[a-z0-9]+(?:[-_:/][a-z0-9]+)*$

    Tweak:
      type: string
      minLength: 1
      maxLength: 255
      pattern: ^[a-z0-9:_\-/]+$

    Generation:
      type: integer
      format: int32
      minimum: 1

    Cursor:
      type: string
      minLength: 1

    Base64:
      type: string
      format: byte

    NonEmptyBase64:
      type: string
      format: byte
      minLength: 1

    KeyAlgorithm:
      type: string
      minLength: 1
      maxLength: 64
      pattern: ^[A-Za-z0-9][A-Za-z0-9._-]*$
      description: Key algorithm exposed by an installed platform. Built-in values are SECP256K1, ED25519, P256, MLDSA44, MLDSA65, and MLDSA87.

    KeyGenMode:
      type: string
      enum: [CREATE, ROTATE, REFRESH]
      description: |
        CREATE starts a new cryptographic identity. ROTATE replaces its key material.
        REFRESH keeps the aggregate key unchanged: threshold ECC replaces peer shares,
        while ML-DSA carries the existing shares forward unchanged.

    Generate:
      type: object
      additionalProperties: false
      description: |
        Key lifecycle request. Mono mode creates or updates local full key material.
        Threshold mode runs distributed key generation and stores peer shares plus commitments.
      required: [keyId, algorithm, authorities, mode]
      properties:
        keyId:
          $ref: '#/components/schemas/KeyId'
        algorithm:
          $ref: '#/components/schemas/KeyAlgorithm'
        authorities:
          type: array
          minItems: 1
          maxItems: 255
          items:
            $ref: '#/components/schemas/KeySetAuthority'
        mode:
          $ref: '#/components/schemas/KeyGenMode'
        policy:
          allOf:
            - $ref: '#/components/schemas/KeySetPolicy'
          nullable: true
        assetOwner:
          allOf:
            - $ref: '#/components/schemas/AssetOwner'
          nullable: true
        approvals:
          $ref: '#/components/schemas/Approvals'

    Store:
      type: object
      additionalProperties: false
      description: |
        Trusted dealer import request. Mono mode stores the imported key locally.
        Threshold mode splits the imported key into peer shares.
      required: [keyId, algorithm, authorities, value64]
      properties:
        keyId:
          $ref: '#/components/schemas/KeyId'
        algorithm:
          $ref: '#/components/schemas/KeyAlgorithm'
        authorities:
          type: array
          minItems: 1
          maxItems: 255
          items:
            $ref: '#/components/schemas/KeySetAuthority'
        value64:
          allOf:
            - $ref: '#/components/schemas/NonEmptyBase64'
          description: Base64 raw private key bytes. For Ed25519, use the standard seed bytes.
        policy:
          allOf:
            - $ref: '#/components/schemas/KeySetPolicy'
          nullable: true
        assetOwner:
          allOf:
            - $ref: '#/components/schemas/AssetOwner'
          nullable: true

    QuorumPromotion:
      type: object
      additionalProperties: false
      description: Target threshold/MPC quorum for mono promotion.
      required: [threshold, total]
      properties:
        threshold:
          type: integer
          format: int32
          minimum: 2
          maximum: 255
          description: Target threshold. Must be greater than 1.
        total:
          type: integer
          format: int32
          minimum: 2
          maximum: 255
          description: Target total peers. Must be at least `threshold`. The promoted keeper becomes peer 1.

    QuorumPromotionResult:
      type: object
      description: Result of mono to threshold promotion.
      required: [peerId, threshold, total, promotedKeys, restartRequired]
      properties:
        peerId:
          type: integer
          format: int32
          description: New peer id of the promoted keeper. It is always 1.
        threshold:
          type: integer
          format: int32
        total:
          type: integer
          format: int32
        promotedKeys:
          type: integer
          format: int32
        restartRequired:
          type: boolean
          description: True when the promoted keeper must be restarted before normal operations.

    KeySetAuthority:
      type: object
      additionalProperties: false
      required: [id]
      properties:
        id:
          $ref: '#/components/schemas/AuthorityId'
        oci:
          type: string
          minLength: 1
          nullable: true

    KeySetPolicy:
      type: object
      additionalProperties: false
      properties:
        apply:
          allOf:
            - $ref: '#/components/schemas/NotAfter'
          nullable: true
        process:
          allOf:
            - $ref: '#/components/schemas/NotAfter'
          nullable: true
        fourEye:
          allOf:
            - $ref: '#/components/schemas/FourEyeControlPolicy'
          nullable: true
        allowHistoricalProcess:
          type: boolean
          default: true

    TimestampUnit:
      type: string
      enum: [MILLISECONDS, SECONDS]

    NotAfter:
      type: object
      additionalProperties: false
      required: [unit]
      properties:
        unit:
          $ref: '#/components/schemas/TimestampUnit'
        notAfter:
          type: integer
          format: int64
          nullable: true

    FourEyeControlPolicy:
      type: object
      additionalProperties: false
      required: [m, n, keys]
      properties:
        mode:
          $ref: '#/components/schemas/FourEyeControlMode'
        m:
          type: integer
          format: int32
          minimum: 2
          maximum: 256
        n:
          type: integer
          format: int32
          minimum: 2
          maximum: 256
        keys:
          type: array
          minItems: 2
          maxItems: 256
          uniqueItems: true
          items:
            $ref: '#/components/schemas/ApproverPublicKey'

    FourEyeControlMode:
      type: string
      enum: [STRICT, LENIENT]
      default: STRICT
      description: STRICT protects every supported operation. LENIENT protects ROTATE, REFRESH, and generation destruction.

    ApproverPublicKey:
      type: object
      additionalProperties: false
      required: [algorithm, publicKey64]
      properties:
        algorithm:
          $ref: '#/components/schemas/KeyAlgorithm'
        publicKey64:
          $ref: '#/components/schemas/NonEmptyBase64'

    Approvals:
      type: object
      additionalProperties: false
      properties:
        keeperId:
          type: integer
          format: int32
          default: 0
          minimum: 0
          maximum: 255
        nonce:
          type: string
          default: ''
          maxLength: 255
        timestamp:
          type: integer
          format: int64
        proofs:
          type: array
          maxItems: 256
          items:
            $ref: '#/components/schemas/Proof'
          default: []

    Proof:
      type: object
      additionalProperties: false
      required: [fingerprint, signature64]
      properties:
        fingerprint:
          $ref: '#/components/schemas/NonEmptyBase64'
        signature64:
          $ref: '#/components/schemas/NonEmptyBase64'

    KeyDestroyReference:
      type: object
      additionalProperties: false
      description: Key generation destroy request.
      required: [keyId, generation]
      properties:
        keyId:
          $ref: '#/components/schemas/KeyId'
        generation:
          allOf:
            - $ref: '#/components/schemas/Generation'
          description: Generation to destroy. Current generation cannot be destroyed.
        approvals:
          $ref: '#/components/schemas/Approvals'

    Sign:
      type: object
      additionalProperties: false
      description: Sign request. The command carries the signature scheme and hash.
      required: [keyId, command]
      properties:
        keyId:
          $ref: '#/components/schemas/KeyId'
        command:
          $ref: '#/components/schemas/AuthorityCommand'
        tweak:
          allOf:
            - $ref: '#/components/schemas/Tweak'
          nullable: true
        approvals:
          $ref: '#/components/schemas/Approvals'

    Verify:
      type: object
      additionalProperties: false
      description: |
        Pure cryptographic verification request. If generation is omitted, the active generation is used.
        The command type selects material validation and serialization; no authority or policy is resolved.
        Any supported material type, including typed or arbitrary material, may be verified independently
        of the key's current authority manifest. A valid result proves cryptographic validity only.
      required: [keyId, command, signature64]
      properties:
        keyId:
          $ref: '#/components/schemas/KeyId'
        generation:
          allOf:
            - $ref: '#/components/schemas/Generation'
          nullable: true
        command:
          $ref: '#/components/schemas/VerificationCommand'
        signature64:
          $ref: '#/components/schemas/NonEmptyBase64'
        tweak:
          allOf:
            - $ref: '#/components/schemas/Tweak'
          nullable: true

    AuthorityCommand:
      type: object
      additionalProperties: false
      required: [type, authorityId, artifact]
      properties:
        type:
          type: string
          minLength: 1
          maxLength: 64
          pattern: ^[a-z0-9]+(?:[._-][a-z0-9]+)*$
          description: Extensible command type registered by the core or an enabled feature.
          example: arbitrary
        authorityId:
          $ref: '#/components/schemas/AuthorityId'
        artifact:
          $ref: '#/components/schemas/Artifact'

    VerificationCommand:
      type: object
      additionalProperties: false
      required: [type, artifact]
      properties:
        type:
          type: string
          minLength: 1
          maxLength: 64
          pattern: ^[a-z0-9]+(?:[._-][a-z0-9]+)*$
          description: Material type registered by the core or an enabled feature.
          example: arbitrary
        artifact:
          $ref: '#/components/schemas/Artifact'

    Artifact:
      oneOf:
        - $ref: '#/components/schemas/ArbitraryArtifact'
        - $ref: '#/components/schemas/TypedArtifact'
        - $ref: '#/components/schemas/EvmTransactionArtifact'
        - $ref: '#/components/schemas/BitcoinTransactionArtifact'
        - $ref: '#/components/schemas/X509TbsCertificateArtifact'

    SignatureScheme:
      type: string
      minLength: 1
      maxLength: 64
      pattern: ^[A-Za-z0-9][A-Za-z0-9._-]*$
      description: Signature scheme exposed by an installed platform. Built-in values are ECDSA, EdDSA, SCHNORR, BIP340, TAPROOT, and MLDSA.

    HashMethod:
      type: string
      enum: [NONE, SHA256, SHA512, KECCAK256]

    ArbitraryArtifact:
      type: object
      additionalProperties: false
      required: [scheme, data64]
      properties:
        scheme:
          $ref: '#/components/schemas/SignatureScheme'
        hash:
          $ref: '#/components/schemas/HashMethod'
        data64:
          $ref: '#/components/schemas/Base64'

    TypedArtifact:
      type: object
      additionalProperties: false
      required: [scheme, typed]
      properties:
        scheme:
          $ref: '#/components/schemas/SignatureScheme'
        hash:
          $ref: '#/components/schemas/HashMethod'
        typed:
          type: object
          additionalProperties: true

    EvmTransactionArtifact:
      type: object
      additionalProperties: false
      required: [message64]
      properties:
        message64:
          $ref: '#/components/schemas/NonEmptyBase64'

    BitcoinTransactionArtifact:
      type: object
      additionalProperties: false
      required: [transaction64, previousTransactions64, input]
      properties:
        transaction64:
          $ref: '#/components/schemas/NonEmptyBase64'
        previousTransactions64:
          type: array
          minItems: 1
          items:
            $ref: '#/components/schemas/NonEmptyBase64'
        input:
          type: integer
          format: int32
          minimum: 0
        redeemScript64:
          allOf:
            - $ref: '#/components/schemas/NonEmptyBase64'
          nullable: true
        witnessScript64:
          allOf:
            - $ref: '#/components/schemas/NonEmptyBase64'
          nullable: true
        merkleRoot64:
          allOf:
            - $ref: '#/components/schemas/NonEmptyBase64'
          nullable: true

    X509TbsCertificateArtifact:
      type: object
      additionalProperties: false
      required: [der64]
      properties:
        der64:
          $ref: '#/components/schemas/NonEmptyBase64'

    CalculatedSignature:
      type: object
      required: [type, generation, imposters]
      properties:
        type:
          $ref: '#/components/schemas/SignatureScheme'
        signature64:
          allOf:
            - $ref: '#/components/schemas/NonEmptyBase64'
          nullable: true
        generation:
          type: integer
          format: int32
        imposters:
          type: array
          description: Peers identified as bad during threshold protocols. Empty in mono mode.
          items:
            type: string

    VerifyResult:
      type: object
      required: [valid]
      properties:
        valid:
          type: boolean

    CipherType:
      type: string
      enum: [AES_GCM, CHACHA20_POLY1305]

    Encrypt:
      type: object
      additionalProperties: false
      description: ECIES encrypt request. Encryption uses public key material.
      required: [keyId, plaintext64]
      properties:
        keyId:
          $ref: '#/components/schemas/KeyId'
        algorithm:
          $ref: '#/components/schemas/CipherType'
        plaintext64:
          $ref: '#/components/schemas/Base64'
        tweak:
          allOf:
            - $ref: '#/components/schemas/Tweak'
          nullable: true

    Encrypted:
      type: object
      required: [ciphertext64, generation]
      properties:
        ciphertext64:
          $ref: '#/components/schemas/NonEmptyBase64'
        generation:
          $ref: '#/components/schemas/Generation'

    Decrypt:
      type: object
      additionalProperties: false
      description: ECIES decrypt request. Mono mode decrypts locally; threshold mode uses peer partial decrypts.
      required: [keyId, ciphertext64]
      properties:
        keyId:
          $ref: '#/components/schemas/KeyId'
        algorithm:
          $ref: '#/components/schemas/CipherType'
        generation:
          allOf:
            - $ref: '#/components/schemas/Generation'
          nullable: true
        ciphertext64:
          $ref: '#/components/schemas/NonEmptyBase64'
        tweak:
          allOf:
            - $ref: '#/components/schemas/Tweak'
          nullable: true
        approvals:
          $ref: '#/components/schemas/Approvals'

    Decrypted:
      type: object
      required: [plaintext64, imposters]
      properties:
        plaintext64:
          $ref: '#/components/schemas/Base64'
        imposters:
          type: array
          description: Peers that returned invalid partial decrypt proofs. Empty in mono mode.
          items:
            type: string

    AssetInventoryPage:
      type: object
      required: [inventory, hasMore]
      properties:
        inventory:
          $ref: '#/components/schemas/AssetInventory'
        nextCursor:
          type: string
          nullable: true
        hasMore:
          type: boolean

    AssetInventory:
      type: object
      required: [generatedAt, peerId, threshold, totalPeers, items]
      properties:
        generatedAt:
          type: integer
          format: int64
        peerId:
          type: integer
          format: int32
        threshold:
          type: integer
          format: int32
        totalPeers:
          type: integer
          format: int32
        items:
          type: array
          items:
            $ref: '#/components/schemas/AssetInventoryItem'

    AssetInventoryItem:
      type: object
      required:
        - logicalId
        - status
        - currentGeneration
        - authorities
        - createdAt
        - updatedAt
        - hasActiveKey
        - tampered
      properties:
        logicalId:
          type: string
        status:
          $ref: '#/components/schemas/KeyStatus'
        currentGeneration:
          type: integer
          format: int32
        authorities:
          type: array
          items:
            $ref: '#/components/schemas/KeySetAuthority'
        algorithm:
          allOf:
            - $ref: '#/components/schemas/KeyAlgorithm'
          nullable: true
        createdAt:
          type: integer
          format: int64
        updatedAt:
          type: integer
          format: int64
        policy:
          allOf:
            - $ref: '#/components/schemas/KeySetPolicy'
          nullable: true
        hasActiveKey:
          type: boolean
        lastPendingGeneration:
          type: integer
          format: int32
          nullable: true
        assetOwner:
          type: string
          nullable: true
        tampered:
          type: boolean

    KeyStatus:
      type: string
      description: 'Shows key current status, where UNKNOWN means key is tampered'
      enum: [ACTIVE, DISABLED, APPLY_EXPIRED, EXPIRED, DESTROYED, UNKNOWN]

    SignedLine:
      type: object
      additionalProperties: false
      required: [event, signature]
      properties:
        event:
          $ref: '#/components/schemas/AuditEvent'
        signature:
          $ref: '#/components/schemas/NonEmptyBase64'

    Logs:
      type: object
      additionalProperties: false
      required: [logs]
      properties:
        logs:
          type: array
          minItems: 1
          maxItems: 1000
          items:
            $ref: '#/components/schemas/SignedLine'
          default: []

    AuditEvent:
      type: object
      additionalProperties: false
      required: [id, peerId, integrityKeyVersion, timestamp, event]
      properties:
        id:
          type: string
          minLength: 1
          maxLength: 255
        peerId:
          type: integer
          format: int32
          minimum: 1
          maximum: 255
        integrityKeyVersion:
          type: integer
          format: int32
          minimum: 1
        timestamp:
          type: integer
          format: int64
          minimum: 0
        event:
          type: string
          minLength: 1
          maxLength: 255
        auth:
          allOf:
            - $ref: '#/components/schemas/Auth'
          nullable: true
        context:
          allOf:
            - $ref: '#/components/schemas/Ctx'
          nullable: true
        request:
          allOf:
            - $ref: '#/components/schemas/Http'
          nullable: true
        crypto:
          allOf:
            - $ref: '#/components/schemas/Crypto'
          nullable: true
        digest:
          allOf:
            - $ref: '#/components/schemas/Dig'
          nullable: true
        outcome:
          allOf:
            - $ref: '#/components/schemas/Outcome'
          nullable: true
        approvers:
          type: array
          nullable: true
          items:
            type: string
        policy:
          allOf:
            - $ref: '#/components/schemas/PolicyEvaluation'
          nullable: true
        imposters:
          type: array
          nullable: true
          items:
            type: string
        dead:
          type: array
          nullable: true
          items:
            type: string

    Auth:
      type: object
      additionalProperties: false
      required: [subject]
      properties:
        subject:
          type: string
          minLength: 1
        actor:
          type: string
          minLength: 1
          nullable: true
          description: Original external actor when subject is an authenticated keeper peer

    Ctx:
      type: object
      additionalProperties: false
      properties:
        sid:
          type: string
          nullable: true

    Http:
      type: object
      additionalProperties: false
      properties:
        method:
          type: string
          nullable: true
        path:
          type: string
          nullable: true
        remoteAddress:
          type: string
          nullable: true

    Crypto:
      type: object
      additionalProperties: false
      properties:
        algo:
          type: string
          nullable: true
        kid:
          type: string
          nullable: true
        generation:
          allOf:
            - $ref: '#/components/schemas/Generation'
          nullable: true

    Dig:
      type: object
      additionalProperties: false
      properties:
        purpose:
          type: string
          nullable: true
        hmacKeyVersion:
          type: integer
          format: int32
          minimum: 1
          nullable: true
        bodyHash:
          allOf:
            - $ref: '#/components/schemas/Digest'
          nullable: true

    Digest:
      type: object
      additionalProperties: false
      required: [alg, value64]
      properties:
        alg:
          type: string
          minLength: 1
        value64:
          $ref: '#/components/schemas/NonEmptyBase64'

    Outcome:
      type: object
      additionalProperties: false
      properties:
        statusCode:
          type: integer
          format: int32
          minimum: 100
          maximum: 599
          nullable: true
        error:
          allOf:
            - $ref: '#/components/schemas/ErrorType'
          nullable: true
        details:
          type: string
          nullable: true
        approvals:
          type: array
          nullable: true
          items:
            $ref: '#/components/schemas/RequiredApprovalDetails'

    RequiredApprovalDetails:
      type: object
      additionalProperties: false
      required: [policyId, source, threshold]
      properties:
        policyId:
          type: string
          minLength: 1
        source:
          type: string
          minLength: 1
        threshold:
          type: integer
          format: int32
          minimum: 1

    PolicyEvaluation:
      type: object
      additionalProperties: false
      required: [decision, matches, approvalRequirements]
      properties:
        decision:
          type: string
          enum: [ALLOW, ALLOW_WITH_REQUIREMENTS, DENY]
        matches:
          type: array
          items:
            $ref: '#/components/schemas/RuleMatch'
        approvalRequirements:
          type: array
          items:
            $ref: '#/components/schemas/ApprovalRequirement'

    ApprovalRequirement:
      type: object
      additionalProperties: false
      required: [policyId, source, threshold, approvers]
      properties:
        policyId:
          type: string
          minLength: 1
        source:
          type: string
          minLength: 1
        threshold:
          type: integer
          format: int32
          minimum: 1
        approvers:
          type: object
          additionalProperties:
            $ref: '#/components/schemas/ApproverPublicKey'

    RuleMatch:
      type: object
      additionalProperties: false
      required: [id, effect]
      properties:
        id:
          type: string
          minLength: 1
        effect:
          type: string
          enum: [ALLOW, ALLOW_WITH_REQUIREMENTS, DENY]

    ConsistencyCheck:
      type: object
      required: [verdict, versions]
      properties:
        verdict:
          $ref: '#/components/schemas/ConsistencyVerdict'
        targetGeneration:
          type: integer
          format: int32
          nullable: true
        versions:
          type: object
          additionalProperties:
            $ref: '#/components/schemas/KeyVersion'

    ConsistencyVerdict:
      type: string
      enum: [OK, SYNC_NEEDED, ROTATE_NEEDED, MISSING]

    KeyVersion:
      type: object
      properties:
        activeGen:
          type: integer
          format: int32
          nullable: true
        pendingGen:
          type: integer
          format: int32
          nullable: true

    ExpirePage:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/ExpireItem'
        next:
          type: string
          nullable: true

    ExpireItem:
      type: object
      required: [type, logicalId, generation, expiresAt]
      properties:
        type:
          type: string
          enum: [APPLY, PROCESS]
        logicalId:
          type: string
        generation:
          type: integer
          format: int32
        expiresAt:
          type: integer
          format: int64

    ControlPlaneAuthConfig:
      oneOf:
        - $ref: '#/components/schemas/HeaderTokenAuthConfig'
        - $ref: '#/components/schemas/OIDCAuthConfig'
      discriminator:
        propertyName: id
        mapping:
          TOKEN: '#/components/schemas/HeaderTokenAuthConfig'
          OIDC: '#/components/schemas/OIDCAuthConfig'

    ControlPlaneCapabilities:
      type: object
      required: [algorithms]
      properties:
        algorithms:
          type: array
          uniqueItems: true
          description: Key algorithms registered by the platforms included in this build.
          items:
            $ref: '#/components/schemas/KeyAlgorithm'

    HeaderTokenAuthConfig:
      type: object
      required: [id, header]
      properties:
        id:
          type: string
          enum: [TOKEN]
        header:
          type: string

    OIDCAuthConfig:
      type: object
      required: [id, header, clientId, audience, discoveryUrl, callbackUrl]
      properties:
        id:
          type: string
          enum: [OIDC]
        header:
          type: string
        clientId:
          type: string
        audience:
          type: string
        discoveryUrl:
          type: string
        callbackUrl:
          type: string

    SystemInfo:
      type: object
      required: [id, state, threshold, totalPeers, peers]
      properties:
        id:
          type: string
        state:
          $ref: '#/components/schemas/PeerState'
        threshold:
          type: integer
          format: int32
        totalPeers:
          type: integer
          format: int32
        peers:
          type: object
          additionalProperties:
            $ref: '#/components/schemas/PeerState'

    PeerState:
      type: string
      enum: [READY, NOT_READY, UNAVAILABLE]

    AuditInfo:
      type: object
      required: [enabled, sinks]
      properties:
        enabled:
          type: boolean
        sinks:
          type: array
          items:
            $ref: '#/components/schemas/SinkInfo'

    SinkInfo:
      type: object
      required: [id, available]
      properties:
        id:
          type: string
        available:
          type: boolean

    SimpleAuthData:
      type: object
      required: [subject, actor, permissions]
      properties:
        subject:
          type: string
        actor:
          type: string
        permissions:
          type: array
          items:
            type: string
