openapi: 3.1.0
info:
  title: XFEServerManager API
  version: 1.0.0
  description: |
    Server-local management API. All timestamps use RFC 3339 UTC. Mutations are
    audited and accept an Idempotency-Key. Long operations return 202 and publish
    progress on the SSE event stream. Optional v2 and player-join features report
    configured and effective state separately. Runtime-safe feature switches hot-apply;
    listener, proxy, executor/session and OpenMetrics initialization changes are
    persisted atomically and require a graceful restart. Legacy message fields are
    migration inputs only; editable messages live exclusively in trigger groups.
  license:
    name: MIT
    identifier: MIT
servers:
  - url: /api/v1
    description: Same-origin management API
tags:
  - name: auth
    description: Owner bootstrap, sessions, TOTP, recent reauthentication and owner-gated account RBAC.
  - name: system
    description: Process health and lifecycle endpoints.
  - name: status
    description: Live server performance and population status.
  - name: players
    description: Online player inspection and safe operations.
  - name: policies
    description: Versioned command policy validation and publication.
  - name: audit
    description: Redacted immutable management audit trail.
  - name: operations
    description: Asynchronous jobs and server-sent events.
  - name: console
    description: Owner-only Minecraft command console.
  - name: moderation
    description: Case-based moderation with durable sanctions and revocation.
  - name: maintenance
    description: Maintenance admission, announcements and structured schedules.
  - name: triggers
    description: Durable event-condition-action automation with visual and XFE Script editors.
  - name: menus
    description: Server-authoritative visual UI menus, image assets, submenus and trigger bindings.
  - name: economy
    description: Exact multi-currency accounts, atomic transfers and an immutable transaction ledger.
  - name: claims
    description: Persistent claims, membership and boundary protection.
  - name: world
    description: Persistent world-event lookup.
  - name: rollback
    description: Previewed, resumable and conflict-aware world rollback.
security:
  - sessionCookie: []
paths:
  /meta:
    get:
      summary: Read public API and first-owner setup state
      operationId: getApiMetadata
      tags: [system]
      security: []
      responses:
        "200":
          description: Public control-plane metadata
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiMetadata"
        default: {$ref: "#/components/responses/Problem"}
  /setup:
    post:
      summary: Create the first owner with a physical-console token
      operationId: setupInitialOwner
      tags: [auth]
      security: []
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/SetupRequest"}
      responses:
        "201":
          description: First owner created, token consumed, and session cookie issued
          headers:
            Set-Cookie: {schema: {type: string}}
            X-Request-ID: {$ref: "#/components/headers/RequestId"}
          content:
            application/json:
              schema: {$ref: "#/components/schemas/SessionInfo"}
        default: {$ref: "#/components/responses/Problem"}
  /auth/bootstrap:
    post:
      summary: Deprecated alias for /setup
      operationId: bootstrapOwner
      tags: [auth]
      deprecated: true
      security: []
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SetupRequest"
      responses:
        "201":
          description: First owner created, token consumed, and session cookie issued
          headers:
            Set-Cookie: {schema: {type: string}}
            X-Request-ID: {$ref: "#/components/headers/RequestId"}
          content:
            application/json:
              schema: {$ref: "#/components/schemas/SessionInfo"}
        default: {$ref: "#/components/responses/Problem"}
  /auth/login:
    post:
      summary: Start an authenticated session
      operationId: login
      tags: [auth]
      security: []
      parameters:
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [username, password]
              properties:
                username: {type: string}
                password: {type: string, maxLength: 1024}
                totp: {type: [string, "null"], pattern: "^[0-9]{6}$"}
                recoveryCode: {type: [string, "null"]}
      responses:
        "200":
          description: Session cookie and CSRF token issued
          headers:
            Set-Cookie: {schema: {type: string}}
            X-Request-ID: {$ref: "#/components/headers/RequestId"}
          content:
            application/json:
              schema: {$ref: "#/components/schemas/SessionInfo"}
        "429":
          description: Login or account-wide second-factor attempt limit reached
          headers:
            Retry-After: {schema: {type: integer, example: 600}}
          content:
            application/problem+json: {schema: {$ref: "#/components/schemas/Problem"}}
        default: {$ref: "#/components/responses/Problem"}
  /auth/session:
    get:
      summary: Read the current session or anonymous setup state
      operationId: getCurrentSession
      tags: [auth]
      security: []
      responses:
        "200":
          description: Current browser session state
          content:
            application/json:
              schema: {$ref: "#/components/schemas/SessionInfo"}
        default: {$ref: "#/components/responses/Problem"}
  /auth/logout:
    post:
      summary: Revoke the current session
      operationId: logout
      tags: [auth]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "204": {description: Session revoked}
        default: {$ref: "#/components/responses/Problem"}
  /auth/totp/enrollment:
    post:
      summary: Begin TOTP enrollment
      operationId: beginTotpEnrollment
      tags: [auth]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [password]
              properties:
                password: {type: string, maxLength: 1024}
      responses:
        "200":
          description: Unconfirmed TOTP secret and provisioning URI
          headers:
            X-Request-ID: {$ref: "#/components/headers/RequestId"}
          content:
            application/json:
              schema:
                type: object
                required: [secret, provisioningUri, expiresAt]
                properties:
                  secret: {type: string}
                  provisioningUri: {type: string, format: uri}
                  expiresAt: {type: string, format: date-time}
        default: {$ref: "#/components/responses/Problem"}
  /auth/totp/confirm:
    post:
      summary: Confirm TOTP and issue recovery codes
      operationId: confirmTotpEnrollment
      tags: [auth]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [code]
              properties:
                code: {type: string, pattern: "^[0-9]{6}$"}
      responses:
        "200":
          description: One-time recovery codes; retrying the same idempotency key repeats only this response
          headers:
            X-Request-ID: {$ref: "#/components/headers/RequestId"}
          content:
            application/json:
              schema:
                type: object
                required: [recoveryCodes]
                properties:
                  recoveryCodes: {type: array, minItems: 8, items: {type: string}}
        "429":
          description: Three unsuccessful second-factor attempts were made for this account within ten minutes
          headers:
            Retry-After: {schema: {type: integer, example: 600}}
          content:
            application/problem+json: {schema: {$ref: "#/components/schemas/Problem"}}
        default: {$ref: "#/components/responses/Problem"}
  /auth/reauthenticate:
    post:
      summary: Refresh recent-authentication assurance with TOTP
      operationId: reauthenticate
      tags: [auth]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                password:
                  type: [string, "null"]
                  maxLength: 1024
                  description: Optional legacy reauthentication path; the Web UI uses TOTP only.
                totp: {type: string, pattern: "^[0-9]{6}$"}
                recoveryCode: {type: [string, "null"]}
      responses:
        "204":
          description: The current server-side session now has recent-authentication assurance
        "429":
          description: Three unsuccessful second-factor attempts were made for this account within ten minutes
          headers:
            Retry-After: {schema: {type: integer, example: 600}}
          content:
            application/problem+json: {schema: {$ref: "#/components/schemas/Problem"}}
        default: {$ref: "#/components/responses/Problem"}
  /auth/sessions:
    get:
      summary: List account sessions
      operationId: listSessions
      tags: [auth]
      responses:
        "200":
          description: Active sessions with redacted source information
          headers:
            X-Request-ID: {$ref: "#/components/headers/RequestId"}
          content:
            application/json:
              schema:
                type: array
                items: {$ref: "#/components/schemas/Session"}
        default: {$ref: "#/components/responses/Problem"}
  /auth/sessions/{sessionId}:
    delete:
      summary: Revoke an account session
      operationId: revokeSession
      tags: [auth]
      parameters:
        - name: sessionId
          in: path
          required: true
          schema: {type: string, format: uuid}
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "204": {description: Session revoked or already absent}
        default: {$ref: "#/components/responses/Problem"}
  /accounts:
    get:
      summary: List Web management accounts
      operationId: listAccounts
      tags: [auth]
      description: Owner-only. Password hashes, TOTP secrets and recovery codes are never returned.
      responses:
        "200":
          description: Safety-filtered management accounts
          content:
            application/json:
              schema:
                type: array
                maxItems: 100
                items: {$ref: "#/components/schemas/Account"}
        default: {$ref: "#/components/responses/Problem"}
    post:
      summary: Create a Web management account
      operationId: createAccount
      tags: [auth]
      description: Owner-only; requires a TOTP-authenticated session and recent password authentication.
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/AccountCreate"}
      responses:
        "201":
          description: Account created
          content:
            application/json:
              schema: {$ref: "#/components/schemas/Account"}
        default: {$ref: "#/components/responses/Problem"}
  /accounts/{accountId}:
    patch:
      summary: Change an account role or disabled state
      operationId: updateAccount
      tags: [auth]
      description: Owner-only with TOTP and recent authentication. Demoting or disabling the last owner is rejected atomically and changing an account revokes its existing sessions.
      parameters:
        - $ref: "#/components/parameters/AccountId"
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/AccountPatch"}
      responses:
        "200":
          description: Account updated
          content:
            application/json:
              schema: {$ref: "#/components/schemas/Account"}
        "409": {$ref: "#/components/responses/Problem"}
        default: {$ref: "#/components/responses/Problem"}
    delete:
      summary: Delete a Web management account
      operationId: deleteAccount
      tags: [auth]
      description: Owner-only with TOTP and recent authentication. Deleting the last owner is rejected atomically.
      parameters:
        - $ref: "#/components/parameters/AccountId"
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "204": {description: Account and its sessions deleted}
        "409": {$ref: "#/components/responses/Problem"}
        default: {$ref: "#/components/responses/Problem"}
  /healthz:
    get:
      summary: Read process health
      operationId: getHealth
      tags: [system]
      security: []
      servers:
        - url: /
      responses:
        "200":
          description: Process health (not authorization readiness)
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Health"
        default:
          $ref: "#/components/responses/Problem"
  /events:
    get:
      summary: Stream management events
      operationId: streamEvents
      tags: [operations]
      parameters:
        - $ref: "#/components/parameters/LastEventId"
      responses:
        "200":
          description: Server-sent events. Event types include status, operation, alert, policy and gap.
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            text/event-stream:
              schema:
                type: string
        default:
          $ref: "#/components/responses/Problem"
  /status:
    get:
      summary: Read server status
      operationId: getServerStatus
      tags: [status]
      responses:
        "200":
          description: Current server status snapshot
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServerStatus"
        default:
          $ref: "#/components/responses/Problem"
  /features:
    get:
      summary: Read effective feature availability
      operationId: getFeatureFlags
      tags: [system]
      responses:
        "200":
          description: Configuration flags intersected with platform capabilities
          content:
            application/json:
              schema: {$ref: "#/components/schemas/FeatureFlags"}
        default: {$ref: "#/components/responses/Problem"}
  /settings:
    get:
      summary: Read safety-filtered effective settings
      operationId: getSettings
      tags: [system]
      responses:
        "200":
          description: Live settings plus any startup-only configuration waiting for restart
          content:
            application/json:
              schema: {$ref: "#/components/schemas/ServerSettings"}
        default: {$ref: "#/components/responses/Problem"}
    put:
      summary: Save editable server settings
      operationId: updateSettings
      tags: [system]
      description: Owner-only. Requires CSRF, TOTP and recent authentication. Runtime-safe fields hot-apply; restartRequired is true only while startup-only fields differ. Legacy message properties are read-only after their one-time trigger migration.
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/ServerSettings"}
      responses:
        "202":
          description: Configuration persisted and runtime-safe fields applied
          content:
            application/json:
              schema: {$ref: "#/components/schemas/Operation"}
        default: {$ref: "#/components/responses/Problem"}
  /triggers:
    get:
      summary: Read trigger groups and their trigger definitions
      operationId: getTriggerWorkspace
      tags: [triggers]
      responses:
        "200":
          description: Complete trigger workspace
          content:
            application/json:
              schema: {$ref: "#/components/schemas/TriggerWorkspace"}
        default: {$ref: "#/components/responses/Problem"}
    post:
      summary: Create a trigger
      operationId: createTrigger
      tags: [triggers]
      description: Administrator or owner with recent TOTP authentication. Arbitrary command actions additionally require the owner-only console capability.
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/TriggerInput"}
      responses:
        "202":
          description: Trigger created and trigger index refreshed
          content:
            application/json:
              schema: {$ref: "#/components/schemas/TriggerMutationResult"}
        default: {$ref: "#/components/responses/Problem"}
  /triggers/catalog:
    get:
      summary: List supported events, conditions, actions, and searchable template variables
      operationId: getTriggerCatalog
      tags: [triggers]
      responses:
        "200":
          description: Trigger authoring catalog
          content:
            application/json:
              schema: {$ref: "#/components/schemas/TriggerCatalog"}
        default: {$ref: "#/components/responses/Problem"}
  /triggers/validate:
    post:
      summary: Validate a visual trigger program or compile and validate XFE Script without saving
      operationId: validateTriggerProgram
      tags: [triggers]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              oneOf:
                - $ref: "#/components/schemas/TriggerScriptValidationRequest"
                - $ref: "#/components/schemas/TriggerVisualValidationRequest"
      responses:
        "200":
          description: Compiled event-condition-action program
          content:
            application/json:
              schema: {$ref: "#/components/schemas/TriggerValidation"}
        default: {$ref: "#/components/responses/Problem"}
  /triggers/{triggerId}:
    parameters:
      - name: triggerId
        in: path
        required: true
        schema: {type: string, format: uuid}
    put:
      summary: Replace a trigger with optimistic revision checking
      operationId: updateTrigger
      tags: [triggers]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/TriggerInput"
                - type: object
                  required: [expectedRevision]
      responses:
        "202":
          description: Trigger updated and trigger index refreshed
          content:
            application/json:
              schema: {$ref: "#/components/schemas/TriggerMutationResult"}
        "409": {$ref: "#/components/responses/Problem"}
        default: {$ref: "#/components/responses/Problem"}
    delete:
      summary: Delete a trigger
      operationId: deleteTrigger
      tags: [triggers]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [expectedRevision]
              properties:
                expectedRevision: {type: integer, minimum: 0}
      responses:
        "202":
          description: Trigger deleted
          content:
            application/json:
              schema: {$ref: "#/components/schemas/DeletionResult"}
        "409": {$ref: "#/components/responses/Problem"}
        default: {$ref: "#/components/responses/Problem"}
  /trigger-groups:
    post:
      summary: Create a trigger group
      operationId: createTriggerGroup
      tags: [triggers]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/TriggerGroupInput"}
      responses:
        "202":
          description: Trigger group created
          content:
            application/json:
              schema: {$ref: "#/components/schemas/TriggerWorkspace"}
        default: {$ref: "#/components/responses/Problem"}
  /trigger-groups/{groupId}:
    parameters:
      - name: groupId
        in: path
        required: true
        schema: {type: string, format: uuid}
    put:
      summary: Replace a trigger group with optimistic revision checking
      operationId: updateTriggerGroup
      tags: [triggers]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/TriggerGroupInput"
                - type: object
                  required: [expectedRevision]
      responses:
        "202":
          description: Trigger group updated
          content:
            application/json:
              schema: {$ref: "#/components/schemas/TriggerWorkspace"}
        "409": {$ref: "#/components/responses/Problem"}
        default: {$ref: "#/components/responses/Problem"}
    delete:
      summary: Delete a trigger group and its triggers
      operationId: deleteTriggerGroup
      tags: [triggers]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [expectedRevision]
              properties:
                expectedRevision: {type: integer, minimum: 0}
      responses:
        "202":
          description: Trigger group deleted with all contained triggers
          content:
            application/json:
              schema: {$ref: "#/components/schemas/DeletionResult"}
        "409": {$ref: "#/components/responses/Problem"}
        default: {$ref: "#/components/responses/Problem"}
  /menus:
    get:
      summary: List interactive UI menu documents
      operationId: listMenus
      tags: [menus]
      responses:
        "200":
          description: Menu workspace
          content:
            application/json:
              schema: {$ref: "#/components/schemas/MenuWorkspace"}
        default: {$ref: "#/components/responses/Problem"}
    post:
      summary: Create an interactive UI menu
      operationId: createMenu
      tags: [menus]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/MenuInput"}
      responses:
        "202":
          description: Menu saved and immediately available to triggers
          content:
            application/json:
              schema: {$ref: "#/components/schemas/MenuSaveResult"}
        default: {$ref: "#/components/responses/Problem"}
  /menus/catalog:
    get:
      summary: List menu positions, controls, events and compatible menu.control triggers
      operationId: getMenuCatalog
      tags: [menus]
      responses:
        "200":
          description: Menu editor catalog
          content:
            application/json:
              schema: {$ref: "#/components/schemas/MenuCatalog"}
        default: {$ref: "#/components/responses/Problem"}
  /menus/images:
    get:
      summary: Search uploaded images and vanilla/mod item, block and texture resources
      operationId: searchMenuImages
      tags: [menus]
      parameters:
        - name: query
          in: query
          schema: {type: string, maxLength: 128}
      responses:
        "200":
          description: Bounded image search result
          content:
            application/json:
              schema:
                type: object
                required: [items, query]
                properties:
                  items: {type: array, maxItems: 200, items: {$ref: "#/components/schemas/MenuImage"}}
                  query: {type: string}
        default: {$ref: "#/components/responses/Problem"}
  /menus/images/preview:
    get:
      summary: Resolve an uploaded, vanilla, or installed-mod image reference for browser preview
      operationId: getMenuImagePreview
      tags: [menus]
      parameters:
        - name: reference
          in: query
          required: true
          schema:
            type: string
            maxLength: 256
            pattern: '^(uploaded|texture|item|block):'
      responses:
        "200":
          description: Bounded PNG/JPEG preview represented as a data URL
          content:
            application/json:
              schema:
                type: object
                required: [reference, mimeType, dataUrl]
                properties:
                  reference: {type: string}
                  mimeType: {type: string, enum: [image/png, image/jpeg]}
                  dataUrl: {type: string}
        "404": {$ref: "#/components/responses/Problem"}
        default: {$ref: "#/components/responses/Problem"}
  /menus/{menuId}:
    parameters:
      - name: menuId
        in: path
        required: true
        schema: {type: string, format: uuid}
    put:
      summary: Replace a menu with optimistic revision checking
      operationId: updateMenu
      tags: [menus]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/MenuInput"
                - type: object
                  required: [expectedRevision]
                  properties:
                    expectedRevision: {type: integer, minimum: 0}
      responses:
        "202":
          description: Menu updated
          content:
            application/json:
              schema: {$ref: "#/components/schemas/MenuSaveResult"}
        "409": {$ref: "#/components/responses/Problem"}
        default: {$ref: "#/components/responses/Problem"}
    delete:
      summary: Delete an unreferenced menu
      operationId: deleteMenu
      tags: [menus]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [expectedRevision]
              properties:
                expectedRevision: {type: integer, minimum: 0}
      responses:
        "202": {description: Menu deleted}
        "409": {$ref: "#/components/responses/Problem"}
        default: {$ref: "#/components/responses/Problem"}
  /menu-assets:
    post:
      summary: Upload a bounded PNG or JPEG menu image
      operationId: uploadMenuImage
      tags: [menus]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, dataUrl]
              properties:
                name: {type: string, minLength: 1, maxLength: 128}
                dataUrl: {type: string, description: Base64 PNG/JPEG data URL; decoded content is limited to 256 KiB.}
      responses:
        "202":
          description: Image stored
          content:
            application/json:
              schema: {$ref: "#/components/schemas/MenuImage"}
        default: {$ref: "#/components/responses/Problem"}
  /menu-assets/{assetId}:
    parameters:
      - name: assetId
        in: path
        required: true
        schema: {type: string, format: uuid}
    delete:
      summary: Delete an image that is not referenced by a menu
      operationId: deleteMenuImage
      tags: [menus]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "202": {description: Image deleted}
        "409": {$ref: "#/components/responses/Problem"}
        default: {$ref: "#/components/responses/Problem"}
  /menu-assets/{assetId}/content:
    get:
      summary: Read one uploaded image as a data URL for the Web editor
      operationId: getMenuImageContent
      tags: [menus]
      parameters:
        - name: assetId
          in: path
          required: true
          schema: {type: string, format: uuid}
      responses:
        "200":
          description: Image data
          content:
            application/json:
              schema:
                type: object
                required: [id, mimeType, dataUrl]
                properties:
                  id: {type: string, format: uuid}
                  mimeType: {type: string, enum: [image/png, image/jpeg]}
                  dataUrl: {type: string}
        default: {$ref: "#/components/responses/Problem"}
  /economy:
    get:
      summary: Read the economy workspace and aggregate overview
      operationId: getEconomyWorkspace
      tags: [economy]
      responses:
        "200":
          description: Currencies and aggregate metrics
          content:
            application/json:
              schema: {$ref: "#/components/schemas/EconomyWorkspace"}
        default: {$ref: "#/components/responses/Problem"}
  /economy/currencies:
    get:
      summary: List every configured currency
      operationId: listCurrencies
      tags: [economy]
      responses:
        "200":
          description: Currency definitions, primary first
          content:
            application/json:
              schema: {type: array, maxItems: 64, items: {$ref: "#/components/schemas/Currency"}}
        default: {$ref: "#/components/responses/Problem"}
    post:
      summary: Create a currency
      operationId: createCurrency
      tags: [economy]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/CurrencyInput"}
      responses:
        "202": {description: Currency created and immediately available to accounts and triggers}
        "409": {$ref: "#/components/responses/Problem"}
        default: {$ref: "#/components/responses/Problem"}
  /economy/currencies/{currencyId}:
    parameters:
      - name: currencyId
        in: path
        required: true
        schema: {type: string, format: uuid}
    put:
      summary: Replace a currency with optimistic revision checking
      operationId: updateCurrency
      tags: [economy]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              allOf:
                - $ref: "#/components/schemas/CurrencyInput"
                - type: object
                  required: [expectedRevision]
                  properties:
                    expectedRevision: {type: integer, minimum: 0}
      responses:
        "202": {description: Currency updated}
        "409": {$ref: "#/components/responses/Problem"}
        default: {$ref: "#/components/responses/Problem"}
    delete:
      summary: Delete an unused non-primary currency
      operationId: deleteCurrency
      tags: [economy]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [expectedRevision, reason]
              properties:
                expectedRevision: {type: integer, minimum: 0}
                reason: {type: string, minLength: 1, maxLength: 512}
      responses:
        "202": {description: Unused currency deleted}
        "409": {$ref: "#/components/responses/Problem"}
        default: {$ref: "#/components/responses/Problem"}
  /economy/accounts:
    get:
      summary: Search player economy accounts
      operationId: listEconomyAccounts
      tags: [economy]
      parameters:
        - {name: query, in: query, schema: {type: string, maxLength: 128}}
        - {name: limit, in: query, schema: {type: integer, minimum: 1, maximum: 200, default: 100}}
      responses:
        "200":
          description: Economy accounts and all currency balances
          content:
            application/json:
              schema:
                type: object
                required: [items]
                properties:
                  items: {type: array, maxItems: 200, items: {$ref: "#/components/schemas/EconomyAccount"}}
                  nextCursor: {type: string}
        default: {$ref: "#/components/responses/Problem"}
  /economy/transactions:
    get:
      summary: Search the immutable economy ledger
      operationId: listEconomyTransactions
      tags: [economy]
      parameters:
        - {name: query, in: query, schema: {type: string, maxLength: 128}}
        - {name: player, in: query, schema: {type: string}}
        - {name: currency, in: query, schema: {type: string}}
        - {name: kind, in: query, schema: {type: string, enum: [initial, deposit, withdraw, set, transfer]}}
        - {name: before, in: query, schema: {type: string, format: date-time}}
        - {name: limit, in: query, schema: {type: integer, minimum: 1, maximum: 200, default: 100}}
      responses:
        "200":
          description: Ledger entries ordered newest first
          content:
            application/json:
              schema:
                type: object
                required: [items]
                properties:
                  items: {type: array, maxItems: 200, items: {$ref: "#/components/schemas/EconomyTransaction"}}
                  nextCursor: {type: string}
        default: {$ref: "#/components/responses/Problem"}
  /economy/adjustments:
    post:
      summary: Deposit, withdraw or set one player's exact balance
      operationId: adjustEconomyBalance
      tags: [economy]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [playerId, playerName, currency, operation, amount, reason]
              properties:
                playerId: {type: string, format: uuid}
                playerName: {type: string, minLength: 1, maxLength: 64}
                currency: {type: string, description: Currency UUID or code.}
                operation: {type: string, enum: [deposit, withdraw, set]}
                amount: {$ref: "#/components/schemas/EconomyAmount"}
                reason: {type: string, minLength: 1, maxLength: 512}
                expectedRevision: {type: integer, minimum: 0}
      responses:
        "202":
          description: Balance changed atomically and ledger entry committed
          content: {application/json: {schema: {$ref: "#/components/schemas/EconomyChange"}}}
        "409": {$ref: "#/components/responses/Problem"}
        default: {$ref: "#/components/responses/Problem"}
  /economy/transfers:
    post:
      summary: Atomically transfer currency between two players
      operationId: transferEconomyBalance
      tags: [economy]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [sourcePlayerId, sourcePlayerName, targetPlayerId, targetPlayerName, currency, amount, reason]
              properties:
                sourcePlayerId: {type: string, format: uuid}
                sourcePlayerName: {type: string, minLength: 1, maxLength: 64}
                targetPlayerId: {type: string, format: uuid}
                targetPlayerName: {type: string, minLength: 1, maxLength: 64}
                currency: {type: string, description: Currency UUID or code.}
                amount: {$ref: "#/components/schemas/EconomyAmount"}
                reason: {type: string, minLength: 1, maxLength: 512}
                expectedSourceRevision: {type: integer, minimum: 0}
      responses:
        "202":
          description: Both balances and one ledger entry committed in one transaction
          content: {application/json: {schema: {$ref: "#/components/schemas/EconomyChange"}}}
        "409": {$ref: "#/components/responses/Problem"}
        default: {$ref: "#/components/responses/Problem"}
  /metrics:
    get:
      summary: Export OpenMetrics
      operationId: getMetrics
      tags: [status]
      servers:
        - url: /
      security:
        - metricsBearer: []
      responses:
        "200":
          description: Token-protected OpenMetrics exposition
          headers:
            X-Request-ID: {$ref: "#/components/headers/RequestId"}
          content:
            application/openmetrics-text:
              schema: {type: string}
        default: {$ref: "#/components/responses/Problem"}
  /players:
    get:
      summary: List players
      operationId: listPlayers
      tags: [players]
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
        - name: online
          in: query
          schema:
            type: boolean
        - name: query
          in: query
          description: Case-insensitive player name or UUID search text.
          schema:
            type: string
            maxLength: 128
      responses:
        "200":
          description: Player page
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlayerPage"
        default:
          $ref: "#/components/responses/Problem"
  /players/{playerId}:
    get:
      summary: Read player details
      operationId: getPlayer
      tags: [players]
      parameters:
        - $ref: "#/components/parameters/PlayerId"
      responses:
        "200":
          description: Player details
          headers:
            ETag:
              description: Player snapshot revision
              schema: {type: string}
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlayerDetail"
        default:
          $ref: "#/components/responses/Problem"
  /players/{playerId}/actions:
    post:
      summary: Preview or execute an online-player action
      operationId: executePlayerAction
      tags: [players]
      parameters:
        - $ref: "#/components/parameters/PlayerId"
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PlayerActionRequest"
      responses:
        "202":
          description: Returns PREVIEW_REQUIRED with a frozen target/token, or an accepted/completed operation
          content:
            application/json:
              schema: {$ref: "#/components/schemas/Operation"}
        default:
          $ref: "#/components/responses/Problem"
  /players/{playerId}/inventory:
    get:
      summary: Read an online player's inventory
      operationId: getPlayerInventory
      tags: [players]
      parameters:
        - $ref: "#/components/parameters/PlayerId"
      responses:
        "200":
          description: Online player inventory and ender chest revisions
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InventorySnapshot"
        default:
          $ref: "#/components/responses/Problem"
    patch:
      summary: Compare-and-swap inventory slots
      operationId: updatePlayerInventory
      tags: [players]
      parameters:
        - $ref: "#/components/parameters/PlayerId"
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/InventoryPatch"
      responses:
        "202":
          description: Audited compare-and-swap result and slot diff
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InventoryUpdateResult"
        "409":
          $ref: "#/components/responses/Problem"
        default:
          $ref: "#/components/responses/Problem"
  /players/{playerId}/ender-chest:
    get:
      summary: Read an online player's ender chest
      operationId: getPlayerEnderChest
      tags: [players]
      parameters:
        - $ref: "#/components/parameters/PlayerId"
      responses:
        "200":
          description: Ender-chest slots and compare-and-swap revision
          content:
            application/json:
              schema: {$ref: "#/components/schemas/InventorySnapshot"}
        default: {$ref: "#/components/responses/Problem"}
    patch:
      summary: Compare-and-swap ender-chest slots
      operationId: updatePlayerEnderChest
      tags: [players]
      parameters:
        - $ref: "#/components/parameters/PlayerId"
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/InventoryPatch"}
      responses:
        "202":
          description: Audited compare-and-swap result and slot diff
          content:
            application/json:
              schema: {$ref: "#/components/schemas/InventoryUpdateResult"}
        "409": {$ref: "#/components/responses/Problem"}
        default: {$ref: "#/components/responses/Problem"}
  /policies:
    get:
      summary: List policy versions
      operationId: listPolicies
      tags: [policies]
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
      responses:
        "200":
          description: Immutable policy versions
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PolicyPage"
        default:
          $ref: "#/components/responses/Problem"
    post:
      summary: Create a policy draft
      operationId: createPolicyDraft
      tags: [policies]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PolicyDraft"
      responses:
        "202":
          $ref: "#/components/responses/AcceptedOperation"
        default:
          $ref: "#/components/responses/Problem"
  /policies/{versionId}:
    get:
      summary: Read a policy version
      operationId: getPolicyVersion
      tags: [policies]
      parameters:
        - $ref: "#/components/parameters/VersionId"
      responses:
        "200":
          description: Policy version with validation and diff
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PolicyVersion"
        default:
          $ref: "#/components/responses/Problem"
    delete:
      summary: Discard the current unpublished draft
      operationId: discardPolicyDraft
      tags: [policies]
      parameters:
        - $ref: "#/components/parameters/VersionId"
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PolicyMutationRequest"
      responses:
        "202":
          $ref: "#/components/responses/AcceptedOperation"
        default:
          $ref: "#/components/responses/Problem"
  /policies/{versionId}/validate:
    post:
      summary: Validate a policy draft
      operationId: validatePolicy
      tags: [policies]
      parameters:
        - $ref: "#/components/parameters/VersionId"
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200":
          description: Validation result; this does not publish the policy
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PolicyValidation"
        default:
          $ref: "#/components/responses/Problem"
  /policies/{versionId}/diff:
    get:
      summary: Diff a policy version against another revision
      operationId: diffPolicy
      tags: [policies]
      parameters:
        - $ref: "#/components/parameters/VersionId"
        - name: from
          in: query
          schema: {type: integer, format: int64, minimum: 0}
      responses:
        "200":
          description: Rule-level immutable version diff
          content:
            application/json:
              schema: {type: object, additionalProperties: true}
        default:
          $ref: "#/components/responses/Problem"
  /policies/simulate:
    post:
      summary: Simulate policy decisions
      operationId: simulatePolicy
      tags: [policies]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PolicySimulationRequest"
      responses:
        "200":
          description: Explained active-policy decision without executing the command
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PolicyDecision"
        default:
          $ref: "#/components/responses/Problem"
  /policies/{versionId}/publish:
    post:
      summary: Publish a validated policy
      operationId: publishPolicy
      tags: [policies]
      parameters:
        - $ref: "#/components/parameters/VersionId"
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PolicyPublishRequest"
      responses:
        "202":
          $ref: "#/components/responses/AcceptedOperation"
        default:
          $ref: "#/components/responses/Problem"
  /policies/{versionId}/rollback:
    post:
      summary: Restore a historical policy
      operationId: rollbackPolicy
      tags: [policies]
      parameters:
        - $ref: "#/components/parameters/VersionId"
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PolicyMutationRequest"
      responses:
        "202":
          $ref: "#/components/responses/AcceptedOperation"
        default:
          $ref: "#/components/responses/Problem"
  /audit:
    get:
      summary: Search audit events
      operationId: listAuditEvents
      tags: [audit]
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
        - name: actor
          in: query
          schema: {type: string}
        - name: action
          in: query
          schema: {type: string}
        - name: query
          in: query
          deprecated: true
          description: Compatibility alias for the action filter.
          schema: {type: string}
        - name: target
          in: query
          schema: {type: string}
        - name: requestId
          in: query
          schema: {type: string, pattern: "^[A-Za-z0-9._-]{8,64}$"}
        - name: from
          in: query
          schema: {type: string, format: date-time}
        - name: to
          in: query
          schema: {type: string, format: date-time}
      responses:
        "200":
          description: Redacted audit page
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuditPage"
        default:
          $ref: "#/components/responses/Problem"
  /operations:
    post:
      summary: Submit a structured player or system operation
      operationId: submitOperation
      tags: [operations]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema: {$ref: "#/components/schemas/OperationRequest"}
      responses:
        "202":
          description: Preview requirement, running job, or completed operation
          content:
            application/json:
              schema: {$ref: "#/components/schemas/Operation"}
        default: {$ref: "#/components/responses/Problem"}
  /operations/{operationId}:
    get:
      summary: Read operation status
      operationId: getOperation
      tags: [operations]
      parameters:
        - $ref: "#/components/parameters/OperationId"
      responses:
        "200":
          description: Operation state and result summary
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Operation"
        default:
          $ref: "#/components/responses/Problem"
  /operations/{operationId}/cancel:
    post:
      summary: Cancel an operation
      operationId: cancelOperation
      tags: [operations]
      parameters:
        - $ref: "#/components/parameters/OperationId"
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "202":
          description: Operation tracking or pending preview cancelled
          content:
            application/json:
              schema: {$ref: "#/components/schemas/Operation"}
        default:
          $ref: "#/components/responses/Problem"
  /console/commands:
    post:
      summary: Execute an owner console command
      operationId: executeConsoleCommand
      tags: [console]
      description: Owner-only and disabled by default. The session must have TOTP assurance and recent password reauthentication.
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [command, reason]
              properties:
                command: {type: string, minLength: 1, maxLength: 4096}
                reason: {type: string, minLength: 1, maxLength: 500}
      responses:
        "202":
          $ref: "#/components/responses/AcceptedOperation"
        default:
          $ref: "#/components/responses/Problem"
  /moderation/cases:
    get:
      summary: List moderation cases
      operationId: listModerationCases
      tags: [moderation]
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
        - name: playerId
          in: query
          schema: {type: string, format: uuid}
        - name: active
          in: query
          schema: {type: boolean}
      responses:
        "200":
          description: Moderation case page
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ModerationCasePage"
        default:
          $ref: "#/components/responses/Problem"
    post:
      summary: Open a moderation case
      operationId: createModerationCase
      tags: [moderation]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateModerationCase"
      responses:
        "202":
          $ref: "#/components/responses/AcceptedOperation"
        default:
          $ref: "#/components/responses/Problem"
  /moderation/cases/{caseId}/actions/{actionId}/revoke:
    post:
      summary: Revoke one sanction action in a moderation case
      operationId: revokeModerationAction
      tags: [moderation]
      parameters:
        - $ref: "#/components/parameters/CaseId"
        - $ref: "#/components/parameters/ActionId"
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/Reason"
      responses:
        "202":
          $ref: "#/components/responses/AcceptedOperation"
        default:
          $ref: "#/components/responses/Problem"
  /maintenance:
    get:
      summary: Read maintenance state
      operationId: getMaintenance
      tags: [maintenance]
      responses:
        "200":
          description: Maintenance mode and upcoming plans
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MaintenanceState"
        default:
          $ref: "#/components/responses/Problem"
    put:
      summary: Change maintenance state
      operationId: setMaintenance
      tags: [maintenance]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MaintenanceRequest"
      responses:
        "202":
          $ref: "#/components/responses/AcceptedOperation"
        default:
          $ref: "#/components/responses/Problem"
  /maintenance/schedules:
    post:
      summary: Schedule a structured action
      operationId: createMaintenanceSchedule
      tags: [maintenance]
      description: Schedules only structured actions, never arbitrary console commands. Graceful stops announce their target on creation, every five minutes in the final hour, and every second in the final minute.
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ScheduledAction"
      responses:
        "202":
          $ref: "#/components/responses/AcceptedOperation"
        default:
          $ref: "#/components/responses/Problem"
  /maintenance/announcements:
    post:
      summary: Broadcast an audited announcement immediately
      operationId: announceMaintenanceMessage
      tags: [maintenance]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [message, reason]
              properties:
                message: {type: string, minLength: 1}
                reason: {type: string, minLength: 1, maxLength: 500}
      responses:
        "200": {description: Announcement broadcast}
        default: {$ref: "#/components/responses/Problem"}
  /claims:
    get:
      summary: List claims
      operationId: listClaims
      tags: [claims]
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
        - name: dimension
          in: query
          schema: {type: string}
        - name: ownerId
          in: query
          schema: {type: string, format: uuid}
      responses:
        "200":
          description: Claim page
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ClaimPage"
        default:
          $ref: "#/components/responses/Problem"
    post:
      summary: Create a claim
      operationId: createClaim
      tags: [claims]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateClaim"
      responses:
        "202":
          $ref: "#/components/responses/AcceptedOperation"
        default:
          $ref: "#/components/responses/Problem"
  /claims/{claimId}:
    patch:
      summary: Update a claim
      operationId: updateClaim
      tags: [claims]
      parameters:
        - $ref: "#/components/parameters/ClaimId"
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/merge-patch+json:
            schema:
              $ref: "#/components/schemas/ClaimPatch"
      responses:
        "202":
          $ref: "#/components/responses/AcceptedOperation"
        default:
          $ref: "#/components/responses/Problem"
    delete:
      summary: Delete a claim using an optimistic revision check
      operationId: deleteClaim
      tags: [claims]
      parameters:
        - $ref: "#/components/parameters/ClaimId"
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [revision, reason]
              properties:
                revision: {type: integer, format: int64, minimum: 0}
                reason: {type: string, minLength: 1, maxLength: 500}
      responses:
        "200": {description: Deleted claim snapshot}
        default: {$ref: "#/components/responses/Problem"}
  /claims/{claimId}/transfer:
    post:
      summary: Begin an owner-to-owner claim transfer
      operationId: beginClaimTransfer
      tags: [claims]
      parameters:
        - $ref: "#/components/parameters/ClaimId"
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [actorId, newOwnerId, reason]
              properties:
                actorId: {type: string, format: uuid}
                newOwnerId: {type: string, format: uuid}
                validity: {type: string, default: 10m}
                reason: {type: string, minLength: 1, maxLength: 500}
      responses:
        "200": {description: Short-lived single-use transfer ticket}
        default: {$ref: "#/components/responses/Problem"}
  /claims/transfers/{transferToken}/confirm:
    post:
      summary: Confirm a claim transfer as its intended recipient
      operationId: confirmClaimTransfer
      tags: [claims]
      parameters:
        - name: transferToken
          in: path
          required: true
          schema: {type: string, format: uuid}
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [acceptingPlayerId, reason]
              properties:
                acceptingPlayerId: {type: string, format: uuid}
                reason: {type: string, minLength: 1, maxLength: 500}
      responses:
        "200": {description: Updated claim with its new owner}
        default: {$ref: "#/components/responses/Problem"}
  /world/changes:
    get:
      summary: Search world changes
      operationId: lookupWorldChanges
      tags: [world]
      parameters:
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/Limit"
        - name: actorId
          in: query
          schema: {type: string, format: uuid}
        - name: dimension
          in: query
          schema: {type: string}
        - name: x
          in: query
          schema: {type: integer}
        - name: y
          in: query
          schema: {type: integer}
        - name: z
          in: query
          schema: {type: integer}
        - name: radius
          in: query
          schema: {type: integer, minimum: 0, maximum: 2048}
        - name: claimId
          in: query
          schema: {type: string, format: uuid}
        - name: action
          in: query
          schema: {type: string}
        - name: resourceId
          in: query
          schema: {type: string}
        - name: from
          in: query
          schema: {type: string, format: date-time}
        - name: to
          in: query
          schema: {type: string, format: date-time}
      responses:
        "200":
          description: World event page; gaps are returned explicitly
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorldChangePage"
        default:
          $ref: "#/components/responses/Problem"
  /rollbacks/preview:
    post:
      summary: Preview a rollback
      operationId: previewRollback
      tags: [rollback]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RollbackSelection"
      responses:
        "200":
          description: Dry-run fixed to a watermark; does not modify the world
          headers:
            X-Request-ID:
              $ref: "#/components/headers/RequestId"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RollbackPreview"
        default:
          $ref: "#/components/responses/Problem"
  /rollbacks:
    post:
      summary: Execute a rollback preview
      operationId: executeRollback
      tags: [rollback]
      parameters:
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [previewId, confirmationToken, reason]
              properties:
                previewId: {type: string, format: uuid}
                confirmationToken: {type: string}
                reason: {type: string, minLength: 1, maxLength: 500}
                forceConflicts: {type: boolean, default: false}
                loadChunks: {type: boolean, default: false}
      responses:
        "202":
          $ref: "#/components/responses/AcceptedOperation"
        default:
          $ref: "#/components/responses/Problem"
  /rollbacks/{operationId}/pause:
    post:
      summary: Pause a rollback
      operationId: pauseRollback
      tags: [rollback]
      parameters:
        - $ref: "#/components/parameters/OperationId"
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "202":
          $ref: "#/components/responses/AcceptedOperation"
        default:
          $ref: "#/components/responses/Problem"
  /rollbacks/{operationId}/resume:
    post:
      summary: Resume a rollback
      operationId: resumeRollback
      tags: [rollback]
      parameters:
        - $ref: "#/components/parameters/OperationId"
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "202":
          $ref: "#/components/responses/AcceptedOperation"
        default:
          $ref: "#/components/responses/Problem"
  /rollbacks/{operationId}/cancel:
    post:
      summary: Cancel an active rollback after reconciling any in-flight item
      operationId: cancelRollback
      tags: [rollback]
      parameters:
        - $ref: "#/components/parameters/OperationId"
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200": {description: Cancelled rollback checkpoint}
        default: {$ref: "#/components/responses/Problem"}
  /rollbacks/{operationId}/redo/preview:
    post:
      summary: Preview redo of a completed rollback
      operationId: previewRedoRollback
      tags: [rollback]
      parameters:
        - $ref: "#/components/parameters/OperationId"
        - $ref: "#/components/parameters/CsrfToken"
        - $ref: "#/components/parameters/IdempotencyKey"
      responses:
        "200": {description: Fixed-watermark redo preview}
        default:
          $ref: "#/components/responses/Problem"
components:
  securitySchemes:
    sessionCookie:
      type: apiKey
      in: cookie
      name: XFESM_SESSION
    metricsBearer:
      type: http
      scheme: bearer
  headers:
    RequestId:
      description: Correlation ID generated by the server or accepted from a validated request header
      schema: {type: string, pattern: "^[A-Za-z0-9._-]{8,64}$"}
  parameters:
    Cursor:
      name: cursor
      in: query
      description: Opaque cursor from the previous page; clients must not parse it.
      schema: {type: string}
    Limit:
      name: limit
      in: query
      schema: {type: integer, minimum: 1, maximum: 200, default: 50}
    LastEventId:
      name: Last-Event-ID
      in: header
      schema: {type: string}
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: true
      description: Unique key for this logical mutation; reuse it when retrying an uncertain response.
      schema: {type: string, minLength: 8, maxLength: 128, pattern: "^[A-Za-z0-9._:-]+$"}
    CsrfToken:
      name: X-CSRF-Token
      in: header
      required: true
      description: Session-bound double-submit token required for same-origin mutations.
      schema: {type: string, minLength: 16, maxLength: 256}
    PlayerId:
      name: playerId
      in: path
      required: true
      schema: {type: string, format: uuid}
    AccountId:
      name: accountId
      in: path
      required: true
      schema: {type: string, format: uuid}
    VersionId:
      name: versionId
      in: path
      required: true
      schema: {type: string, pattern: '^policy-[0-9]+$'}
    OperationId:
      name: operationId
      in: path
      required: true
      schema: {type: string, format: uuid}
    CaseId:
      name: caseId
      in: path
      required: true
      schema: {type: string, format: uuid}
    ActionId:
      name: actionId
      in: path
      required: true
      schema: {type: string, format: uuid}
    ClaimId:
      name: claimId
      in: path
      required: true
      schema: {type: string, format: uuid}
  responses:
    Problem:
      description: RFC 9457 problem details
      headers:
        X-Request-ID:
          $ref: "#/components/headers/RequestId"
      content:
        application/problem+json:
          schema:
            $ref: "#/components/schemas/Problem"
    AcceptedOperation:
      description: Preview requirement, accepted job, or operation completed within the bounded dispatch window
      headers:
        Location:
          description: Relative operation status URL
          schema: {type: string}
        X-Request-ID:
          $ref: "#/components/headers/RequestId"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Operation"
  schemas:
    ApiMetadata:
      type: object
      required: [name, apiVersion, setupRequired, serverReady]
      properties:
        name: {type: string}
        apiVersion: {type: string, const: v1}
        setupRequired: {type: boolean}
        serverReady: {type: boolean}
    SetupRequest:
      type: object
      required: [bootstrapToken, username, password]
      properties:
        bootstrapToken: {type: string, minLength: 16, maxLength: 256}
        username: {$ref: "#/components/schemas/AccountUsername"}
        password: {type: string, minLength: 12, maxLength: 1024}
    Actor:
      type: object
      required: [id, displayName, roles, permissions, totpEnabled, totpVerified, reauthenticationRequired]
      properties:
        id: {type: string, format: uuid}
        displayName: {type: string}
        roles:
          type: array
          minItems: 1
          items: {type: string, enum: [observer, helper, moderator, administrator, owner]}
        permissions: {type: array, items: {type: string}}
        totpEnabled: {type: boolean}
        totpVerified: {type: boolean}
        reauthenticationRequired: {type: boolean}
    SessionInfo:
      type: object
      required: [authenticated, bootstrapRequired]
      properties:
        authenticated: {type: boolean}
        actor: {$ref: "#/components/schemas/Actor"}
        csrfToken: {type: string}
        bootstrapRequired: {type: boolean}
        expiresAt: {type: string, format: date-time}
    Session:
      type: object
      required: [id, createdAt, lastSeenAt, expiresAt, current]
      properties:
        id: {type: string, format: uuid}
        createdAt: {type: string, format: date-time}
        lastSeenAt: {type: string, format: date-time}
        expiresAt: {type: string, format: date-time}
        current: {type: boolean}
        source: {type: string, description: Redacted source label}
    AccountUsername:
      type: string
      minLength: 3
      maxLength: 32
      pattern: "^[A-Za-z0-9_.-]+$"
    AccountRole:
      type: string
      enum: [observer, helper, moderator, administrator, owner]
    Account:
      type: object
      required: [id, username, role, totpEnabled, disabled, createdAt, updatedAt]
      properties:
        id: {type: string, format: uuid}
        username: {$ref: "#/components/schemas/AccountUsername"}
        role: {$ref: "#/components/schemas/AccountRole"}
        totpEnabled: {type: boolean}
        disabled: {type: boolean}
        createdAt: {type: string, format: date-time}
        updatedAt: {type: string, format: date-time}
        lastLoginAt: {type: [string, "null"], format: date-time}
    AccountCreate:
      type: object
      additionalProperties: false
      required: [username, password, role]
      properties:
        username: {$ref: "#/components/schemas/AccountUsername"}
        password: {type: string, minLength: 12, maxLength: 1024, writeOnly: true}
        role: {$ref: "#/components/schemas/AccountRole"}
    AccountPatch:
      type: object
      additionalProperties: false
      minProperties: 1
      properties:
        role: {$ref: "#/components/schemas/AccountRole"}
        disabled: {type: boolean}
    Problem:
      type: object
      required: [type, title, status, detail, requestId]
      properties:
        type: {type: string, format: uri-reference}
        title: {type: string}
        status: {type: integer, minimum: 400, maximum: 599}
        detail: {type: string}
        instance: {type: [string, "null"], format: uri-reference}
        requestId: {type: string, pattern: "^[A-Za-z0-9._-]{8,64}$"}
        extensions: {type: object, additionalProperties: true}
    PageMetadata:
      type: object
      required: [hasMore]
      properties:
        nextCursor: {type: [string, "null"]}
        hasMore: {type: boolean}
    Health:
      type: object
      required: [http, serverReady, sseClients]
      properties:
        http: {type: string, enum: [running, starting, stopping, stopped, failed]}
        serverReady: {type: boolean}
        sseClients: {type: integer, minimum: 0}
    ServerStatus:
      type: object
      required: [sampledAt, uptimeSeconds, playersOnline]
      properties:
        sampledAt: {type: string, format: date-time}
        serverName: {type: string}
        minecraftVersion: {type: string}
        forgeVersion: {type: string}
        onlineMode: {type: boolean}
        uptimeSeconds: {type: integer, minimum: 0}
        tps: {type: number, minimum: 0, maximum: 20}
        mspt:
          type: object
          properties:
            average: {type: number, minimum: 0}
            p95: {type: number, minimum: 0}
            p99: {type: number, minimum: 0}
            jitter: {type: number, minimum: 0}
        cpuLoad: {type: number, minimum: 0, maximum: 1}
        heapUsedBytes: {type: integer, minimum: 0}
        heapMaxBytes: {type: integer, minimum: 0}
        gcPauseMs: {type: integer, minimum: 0}
        diskFreeBytes: {type: integer, minimum: 0}
        playersOnline: {type: integer, minimum: 0}
        playersMax: {type: integer, minimum: 0}
        averagePingMs: {type: number, minimum: 0}
        dimensions:
          type: array
          items:
            type: object
            properties:
              id: {type: string}
              loadedChunks: {type: integer, minimum: 0}
              entities: {type: integer, minimum: 0}
        samples:
          type: array
          items:
            type: object
            required: [at]
            properties:
              at: {type: string, format: date-time}
              tps: {type: number, minimum: 0, maximum: 20}
              mspt: {type: number, minimum: 0}
    FeatureFlags:
      type: object
      required: [consoleEnabled, moderationEnabled, claimsEnabled, worldTrackingEnabled, rollbackEnabled, joinExperienceEnabled, metricsEnabled]
      properties:
        consoleEnabled: {type: boolean}
        moderationEnabled: {type: boolean}
        claimsEnabled: {type: boolean}
        worldTrackingEnabled: {type: boolean}
        rollbackEnabled: {type: boolean}
        joinExperienceEnabled: {type: boolean, description: Legacy compatibility status; authoring moved to triggers.}
        metricsEnabled: {type: boolean}
    ServerSettings:
      type: object
      required: [bindAddress, port, trustedProxyCidrs, crashProtection, features, metricsRequireToken, offlineMode, sparkDetected, blueMapDetected]
      properties:
        bindAddress: {type: string}
        port: {type: integer, minimum: 1, maximum: 65535}
        trustedProxyCidrs: {type: array, items: {type: string}}
        publicBaseUrl: {type: string, format: uri}
        idleSessionSeconds: {type: integer, minimum: 1}
        absoluteSessionSeconds: {type: integer, minimum: 1}
        recentAuthenticationSeconds: {type: integer, minimum: 1}
        httpThreads: {type: integer, minimum: 2, maximum: 64}
        maximumSseClients: {type: integer, minimum: 1, maximum: 1000}
        webConsoleEnabled: {type: boolean}
        rawItemEditingEnabled: {type: boolean}
        metricsEnabled: {type: boolean}
        moderationEnabled: {type: boolean}
        claimsEnabled: {type: boolean}
        worldTrackingEnabled: {type: boolean}
        rollbackEnabled: {type: boolean}
        joinExperienceEnabled: {type: boolean, readOnly: true, deprecated: true}
        joinWelcomeEnabled: {type: boolean, readOnly: true, deprecated: true}
        joinWelcomeMessage: {type: string, maxLength: 65536, readOnly: true, deprecated: true}
        firstJoinMessageEnabled: {type: boolean, readOnly: true, deprecated: true}
        firstJoinMessage: {type: string, maxLength: 65536, readOnly: true, deprecated: true}
        dailyAnnouncementsEnabled: {type: boolean, readOnly: true, deprecated: true}
        dailyAnnouncements:
          type: array
          maxItems: 20
          items: {type: string, maxLength: 65536}
          readOnly: true
          deprecated: true
        announcementTimeZone: {type: string, examples: [Asia/Shanghai]}
        defaultMessageSender:
          type: string
          minLength: 1
          maxLength: 64
          default: XFEServerManager
          description: Sender prefix inherited by plain messages and rich messages that do not define an explicit sender. Applied immediately without a restart.
        rulesReminderEnabled: {type: boolean, readOnly: true, deprecated: true}
        rulesMessage: {type: string, maxLength: 65536, readOnly: true, deprecated: true}
        maintenanceJoinReminderEnabled: {type: boolean, readOnly: true, deprecated: true}
        crashProtection: {$ref: "#/components/schemas/CrashProtectionSettings"}
        restartRequired: {type: boolean}
        features: {$ref: "#/components/schemas/FeatureFlags"}
        serverLocale: {type: string}
        metricsRequireToken: {type: boolean, const: true}
        offlineMode: {type: boolean}
        sparkDetected: {type: boolean}
        blueMapDetected: {type: boolean}
    CrashProtectionSettings:
      type: object
      additionalProperties: false
      description: Runtime-safe preemptive limits for entity floods, command-block loops, slow ticks, loaded chunks, and memory pressure. Players and existing living entities are never removed.
      required: [enabled, scanIntervalTicks, itemEntitiesPerDimension, itemEntitiesPerChunk, livingEntitiesPerDimension, livingEntitiesPerChunk, totalEntitiesPerDimension, totalEntitiesPerChunk, entitiesPerNamespacePerDimension, loadedChunksPerDimension, spawnBurstLimit, spawnBurstWindowTicks, commandBlockCommandsPerSecond, slowTickMillis, consecutiveSlowTicks, heapUsagePercent, blockExcessSpawns, removeExcessItems, stopRunawayCommandBlocks]
      properties:
        enabled: {type: boolean, default: true}
        scanIntervalTicks: {type: integer, minimum: 20, maximum: 1200, default: 100}
        itemEntitiesPerDimension: {type: integer, minimum: 1, maximum: 1000000, default: 2000}
        itemEntitiesPerChunk: {type: integer, minimum: 1, maximum: 1000000, default: 256}
        livingEntitiesPerDimension: {type: integer, minimum: 1, maximum: 1000000, default: 2000}
        livingEntitiesPerChunk: {type: integer, minimum: 1, maximum: 1000000, default: 128}
        totalEntitiesPerDimension: {type: integer, minimum: 1, maximum: 2000000, default: 5000}
        totalEntitiesPerChunk: {type: integer, minimum: 1, maximum: 2000000, default: 512}
        entitiesPerNamespacePerDimension: {type: integer, minimum: 1, maximum: 2000000, default: 512}
        loadedChunksPerDimension: {type: integer, minimum: 1, maximum: 1000000, default: 8000}
        spawnBurstLimit: {type: integer, minimum: 1, maximum: 1000000, default: 400}
        spawnBurstWindowTicks: {type: integer, minimum: 20, maximum: 72000, default: 100}
        commandBlockCommandsPerSecond: {type: integer, minimum: 1, maximum: 100000, default: 100}
        slowTickMillis: {type: integer, minimum: 50, maximum: 60000, default: 200}
        consecutiveSlowTicks: {type: integer, minimum: 1, maximum: 1200, default: 3}
        heapUsagePercent: {type: integer, minimum: 50, maximum: 99, default: 90}
        blockExcessSpawns: {type: boolean, default: true}
        removeExcessItems: {type: boolean, default: true}
        stopRunawayCommandBlocks: {type: boolean, default: true}
    PlayerSummary:
      type: object
      required: [uuid, name, online]
      properties:
        uuid: {type: string, format: uuid}
        name: {type: string}
        online: {type: boolean}
        pingMs: {type: integer, minimum: 0}
        roles: {type: array, items: {type: string}}
        opLevel: {type: integer, minimum: 0, maximum: 4}
        gameMode: {type: string}
        health: {type: number, minimum: 0}
        foodLevel: {type: integer, minimum: 0}
        location:
          allOf:
            - $ref: "#/components/schemas/Position"
            - type: object
              required: [dimension]
              properties:
                dimension: {type: string}
    PlayerDetail:
      allOf:
        - $ref: "#/components/schemas/PlayerSummary"
        - type: object
          properties:
            connectedAt: {type: string, format: date-time}
    PlayerPage:
      type: object
      required: [items]
      properties:
        items: {type: array, items: {$ref: "#/components/schemas/PlayerSummary"}}
        nextCursor: {type: string}
    Position:
      type: object
      required: [x, y, z]
      properties:
        x: {type: number}
        y: {type: number}
        z: {type: number}
    Reason:
      type: object
      required: [reason]
      properties:
        reason: {type: string, minLength: 1, maxLength: 500}
    PlayerActionRequest:
      type: object
      required: [action, reason]
      properties:
        action:
          type: string
          enum: [message, kick, teleport, recall, heal, feed, gamemode, clear_effects, whitelist, ban]
        reason: {type: string, minLength: 1, maxLength: 500}
        value: {type: string}
        confirmedTargetUuid:
          type: string
          format: uuid
          deprecated: true
          description: Optional UI cross-check; the path and frozen preview UUID are authoritative.
        previewToken: {type: string, format: uuid}
    ItemStack:
      type: object
      required: [slot, itemId, count, structuredData, opaqueDataPresent, opaqueDataFingerprint]
      properties:
        slot: {type: integer, minimum: 0}
        itemId: {type: string}
        count: {type: integer, minimum: 0}
        structuredData: {type: object, additionalProperties: {type: string}}
        opaqueDataPresent:
          type: boolean
          readOnly: true
          description: True when server-side NBT/data components exist but are deliberately redacted.
        opaqueDataFingerprint:
          type: string
          readOnly: true
          pattern: "^([0-9a-f]{64})?$"
          description: Non-reversible change marker; never a restorable raw payload.
    ItemStackPatch:
      type: object
      additionalProperties: false
      required: [slot, itemId, count, structuredData]
      properties:
        slot: {type: integer, minimum: 0}
        itemId: {type: string}
        count: {type: integer, minimum: 0, maximum: 127}
        structuredData: {type: object, additionalProperties: {type: string}}
    InventorySnapshot:
      type: object
      required: [playerUuid, container, revision, online, slots]
      properties:
        playerUuid: {type: string, format: uuid}
        container: {type: string, enum: [inventory, ender_chest]}
        revision: {type: integer, minimum: 0}
        online: {type: boolean}
        slots: {type: array, items: {$ref: "#/components/schemas/ItemStack"}}
    InventoryPatch:
      type: object
      required: [expectedRevision, updates, reason]
      properties:
        expectedRevision: {type: integer, minimum: 0}
        updates:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/ItemStackPatch"
        reason: {type: string, minLength: 1, maxLength: 500}
        rawEditingRequested: {type: boolean, default: false}
    InventorySlotDiff:
      type: object
      required: [slot, before, after]
      properties:
        slot: {type: integer, minimum: 0}
        before: {$ref: "#/components/schemas/ItemStack"}
        after: {$ref: "#/components/schemas/ItemStack"}
    InventoryUpdateResult:
      allOf:
        - $ref: "#/components/schemas/Operation"
        - type: object
          required: [revision, diffs]
          properties:
            revision: {type: integer, format: int64, minimum: 0}
            diffs:
              type: array
              items: {$ref: "#/components/schemas/InventorySlotDiff"}
    PolicyDraft:
      type: object
      required: [expectedActiveVersion, description, rules]
      properties:
        expectedActiveVersion: {type: integer, format: int64, minimum: 0}
        description: {type: string, minLength: 1, maxLength: 1000}
        rules: {type: array, items: {type: object, additionalProperties: true}}
    PolicyPublishRequest:
      type: object
      required: [version, reason]
      properties:
        version: {type: integer, format: int64, minimum: 1}
        reason: {type: string, minLength: 1, maxLength: 1000}
    PolicyMutationRequest:
      type: object
      required: [expectedActiveVersion, reason]
      properties:
        expectedActiveVersion: {type: integer, format: int64, minimum: 0}
        reason: {type: string, minLength: 1, maxLength: 1000}
    PolicyVersion:
      type: object
      required: [id, version, name, state, groupCount, ruleCount, updatedAt]
      properties:
        id: {type: string, pattern: '^policy-[0-9]+$'}
        version: {type: integer, format: int64, minimum: 0}
        name: {type: string}
        description: {type: string}
        state: {type: string, enum: [DRAFT, ACTIVE, ARCHIVED]}
        groupCount: {type: integer, minimum: 0}
        ruleCount: {type: integer, minimum: 0}
        updatedAt: {type: string, format: date-time}
        updatedBy: {type: string}
        checksum: {type: string}
        rules:
          type: array
          items:
            allOf:
              - $ref: "#/components/schemas/PolicyRule"
        validation: {$ref: "#/components/schemas/PolicyValidation"}
    PolicyRule:
      type: object
      required: [id, enabled, groupId, groupName, groupEnabled, tier, subject, command, effect, constraints, reason]
      properties:
        id: {type: string, pattern: '^[A-Za-z0-9._-]+$'}
        enabled: {type: boolean}
        groupId: {type: string, pattern: '^[A-Za-z0-9._-]{1,64}$'}
        groupName: {type: string, minLength: 1, maxLength: 128}
        groupEnabled: {type: boolean}
        tier: {type: string, enum: [DEFAULT, OP_LEVEL, ROLE, UUID, HARD_SAFETY]}
        subject: {type: object, additionalProperties: true}
        command: {type: object, additionalProperties: true}
        effect: {type: string, enum: [PASS_THROUGH, DENY, GRANT, CONSTRAIN]}
        constraints: {type: object, additionalProperties: true}
        reason: {type: string, minLength: 1, maxLength: 1000}
    PolicyPage:
      type: object
      required: [items, nextCursor]
      properties:
        items: {type: array, items: {$ref: "#/components/schemas/PolicyVersion"}}
        nextCursor: {type: [string, "null"]}
    PolicyValidation:
      type: object
      required: [valid, errors, warnings]
      properties:
        valid: {type: boolean}
        errors:
          type: array
          items: {$ref: "#/components/schemas/PolicyValidationIssue"}
        warnings:
          type: array
          items: {$ref: "#/components/schemas/PolicyValidationIssue"}
    PolicyValidationIssue:
      type: object
      required: [ruleId, code, message]
      properties:
        ruleId: {type: string}
        code: {type: string}
        message: {type: string}
    PolicySimulationRequest:
      type: object
      required: [command]
      properties:
        command: {type: string, minLength: 1, maxLength: 4096}
        playerUuid: {type: [string, "null"], format: uuid}
    PolicyDecision:
      type: object
      required: [decision, explanation, matchedRuleIds, normalizedCommand]
      properties:
        decision: {type: string, enum: [PASS_THROUGH, DENY, GRANT, CONSTRAIN]}
        explanation: {type: array, items: {type: string}}
        matchedRuleIds: {type: array, items: {type: string}}
        normalizedCommand: {type: string}
    AuditEvent:
      type: object
      required: [id, at, actor, source, action, result]
      properties:
        id: {type: string, format: uuid}
        at: {type: string, format: date-time}
        actor: {type: string}
        source: {type: string}
        target: {type: [string, "null"]}
        action: {type: string}
        reason: {type: [string, "null"]}
        requestId: {type: string}
        result: {type: string}
        durationMs: {type: integer, minimum: 0}
    AuditPage:
      type: object
      required: [items, nextCursor]
      properties:
        items: {type: array, items: {$ref: "#/components/schemas/AuditEvent"}}
        nextCursor: {type: [string, "null"]}
    OperationRequest:
      type: object
      required: [action, reason]
      properties:
        action: {type: string}
        target: {}
        reason: {type: string, minLength: 1, maxLength: 512}
        parameters: {type: object, additionalProperties: true}
        previewToken: {type: string, format: uuid}
    Operation:
      type: object
      required: [id, kind, state]
      properties:
        id: {type: string, format: uuid}
        kind: {type: string}
        state: {type: string, enum: [PREVIEW_REQUIRED, QUEUED, RUNNING, PAUSED, SUCCEEDED, PARTIAL, FAILED, CANCELLED]}
        createdAt: {type: string, format: date-time}
        completedAt: {type: string, format: date-time}
        expiresAt: {type: string, format: date-time}
        progress: {type: number, minimum: 0, maximum: 1}
        message: {type: string}
        requestId: {type: string}
        previewToken: {type: string, format: uuid}
        targets:
          type: array
          items:
            type: object
            required: [uuid]
            properties:
              uuid: {type: string, format: uuid}
              name: {type: string}
        result:
          oneOf:
            - {type: object, additionalProperties: true}
            - {type: integer}
        results: {type: array, items: {type: object, additionalProperties: true}}
        revision: {type: integer, minimum: 0}
        diffs: {type: array, items: {type: object, additionalProperties: true}}
        error: {$ref: "#/components/schemas/Problem"}
    CreateModerationCase:
      type: object
      required: [playerId, action, reason]
      properties:
        playerId: {type: string, format: uuid}
        action: {type: string, enum: [warn, mute, kick, tempban, ban]}
        reason: {type: string, minLength: 1, maxLength: 500}
        message: {type: string, minLength: 1, maxLength: 500, description: Player-facing message; defaults to reason when omitted.}
        duration: {type: string, description: "Required for tempban. Examples include 30m, 2h, 7d, or an ISO-8601 duration."}
        templateId: {type: [string, "null"]}
        expiresAt: {type: [string, "null"], format: date-time}
        evidenceUrls: {type: array, items: {type: string, format: uri}}
        staffNote: {type: string, maxLength: 4000}
    ModerationCase:
      type: object
      required: [id, playerId, action, reason, createdAt, active]
      properties:
        id: {type: string, format: uuid}
        playerId: {type: string, format: uuid}
        action: {type: string}
        reason: {type: string}
        message: {type: string}
        createdAt: {type: string, format: date-time}
        expiresAt: {type: [string, "null"], format: date-time}
        active: {type: boolean}
        remainingSeconds: {type: integer, description: "Seconds remaining, or -1 for a permanent/no-expiry action."}
        evidenceUrls: {type: array, items: {type: string, format: uri}}
    ModerationCasePage:
      type: object
      required: [items, page]
      properties:
        items: {type: array, items: {$ref: "#/components/schemas/ModerationCase"}}
        page: {$ref: "#/components/schemas/PageMetadata"}
    MaintenanceState:
      type: object
      required: [enabled, whitelistOnly, schedules, defaultMessageSender]
      properties:
        enabled: {type: boolean}
        whitelistOnly: {type: boolean}
        message: {type: string}
        defaultMessageSender: {type: string, minLength: 1, maxLength: 64}
        endsAt: {type: [string, "null"], format: date-time}
        schedules: {type: array, items: {$ref: "#/components/schemas/ScheduledAction"}}
    MaintenanceRequest:
      type: object
      required: [enabled, reason]
      properties:
        enabled: {type: boolean}
        whitelistOnly: {type: boolean, default: true}
        message: {type: string, maxLength: 500}
        endsAt: {type: [string, "null"], format: date-time}
        countdownSeconds: {type: integer, minimum: 0, maximum: 86400}
        reason: {type: string, minLength: 1, maxLength: 500}
    ScheduledAction:
      type: object
      required: [kind, executeAt, reason]
      properties:
        id: {type: string, format: uuid, readOnly: true}
        kind: {type: string, enum: [announce, enable_maintenance, disable_maintenance, save_flush, graceful_stop]}
        executeAt: {type: string, format: date-time}
        arguments: {type: object, additionalProperties: true}
        reason: {type: string, minLength: 1, maxLength: 500}
        status: {type: string, enum: [PENDING, CLAIMED, COMPLETED, CANCELLED], readOnly: true}
        attempts: {type: integer, minimum: 0, readOnly: true}
        leaseUntil: {type: [string, "null"], format: date-time, readOnly: true}
        completedAt: {type: [string, "null"], format: date-time, readOnly: true}
        lastError: {type: string, readOnly: true}
        createdBy: {type: string, readOnly: true}
    TriggerWorkspace:
      type: object
      required: [groups, totalTriggers]
      properties:
        groups:
          type: array
          maxItems: 256
          items: {$ref: "#/components/schemas/TriggerGroup"}
        totalTriggers: {type: integer, minimum: 0, maximum: 1024}
    TriggerCatalog:
      type: object
      required: [events, operators, actions, actionParameters, variables, variableSyntax, defaultMessageSender]
      properties:
        events: {type: array, items: {type: string}}
        operators: {type: array, items: {type: string}}
        actions: {type: array, items: {type: string}}
        actionParameters:
          type: object
          additionalProperties:
            type: array
            items: {type: string}
        variables:
          type: array
          minItems: 100
          items: {$ref: "#/components/schemas/TriggerVariableDefinition"}
        variableSyntax:
          $ref: "#/components/schemas/TriggerVariableSyntax"
        defaultMessageSender: {type: string, minLength: 1, maxLength: 64}
    TriggerVariableDefinition:
      type: object
      required: [key, nameZh, nameEn, descriptionZh, descriptionEn, category, scopes,
        example, sampleValue, formatHint, type, events, templateAllowed, conditionAllowed]
      properties:
        key:
          type: string
          description: Variable expression without the surrounding braces
        nameZh: {type: string}
        nameEn: {type: string}
        descriptionZh: {type: string}
        descriptionEn: {type: string}
        category: {type: string}
        scopes:
          type: array
          minItems: 1
          items: {type: string}
          description: Semantic UI scopes such as global, player-event, world, command, or schedule
        example:
          type: string
          description: Complete placeholder ready for insertion
        sampleValue:
          type: string
          description: Example resolved value, or an empty string when no fixed sample is useful
        formatHint: {type: string}
        type: {type: string, enum: [string, number, boolean, instant, datetime]}
        events:
          type: array
          minItems: 1
          items: {type: string}
          description: Exact event IDs or bounded glob patterns; `*` means every event
        templateAllowed: {type: boolean}
        conditionAllowed: {type: boolean}
    TriggerVariableSyntax:
      type: object
      required: [delimiter, formattedTime, formatHint]
      properties:
        delimiter: {type: string, example: "{variable}"}
        formattedTime: {type: string, example: "{server.time:uuuu-MM-dd HH:mm:ss}"}
        formatHint: {type: string}
    TriggerMutationResult:
      type: object
      required: [trigger, workspace]
      properties:
        trigger: {$ref: "#/components/schemas/Trigger"}
        workspace: {$ref: "#/components/schemas/TriggerWorkspace"}
    TriggerValidation:
      type: object
      required: [valid, event, conditionMode, conditions, actions]
      properties:
        valid: {type: boolean, const: true}
        event: {$ref: "#/components/schemas/TriggerEvent"}
        conditionMode: {type: string, enum: [all, any]}
        conditions:
          type: array
          items: {$ref: "#/components/schemas/TriggerCondition"}
        actions:
          type: array
          minItems: 1
          description: Root action nodes. Action and condition trees may be nested without an artificial authoring limit.
          items: {$ref: "#/components/schemas/TriggerAction"}
    TriggerScriptValidationRequest:
      type: object
      additionalProperties: false
      required: [script]
      properties:
        script: {type: string, minLength: 1, maxLength: 65536}
    TriggerVisualValidationRequest:
      type: object
      additionalProperties: false
      required: [event, conditionMode, conditions, actions]
      properties:
        event: {$ref: "#/components/schemas/TriggerEvent"}
        conditionMode: {type: string, enum: [all, any]}
        conditions:
          type: array
          items: {$ref: "#/components/schemas/TriggerCondition"}
        actions:
          type: array
          minItems: 1
          description: Root action nodes. Action and condition trees may be nested without an artificial authoring limit.
          items: {$ref: "#/components/schemas/TriggerAction"}
    DeletionResult:
      type: object
      required: [deleted, id]
      properties:
        deleted: {type: boolean, const: true}
        id: {type: string, format: uuid}
    TriggerGroup:
      allOf:
        - $ref: "#/components/schemas/TriggerGroupInput"
        - type: object
          required: [id, revision, triggers, createdBy, createdAt, updatedAt, migrated]
          properties:
            id: {type: string, format: uuid}
            revision: {type: integer, minimum: 0}
            triggers: {type: array, maxItems: 256, items: {$ref: "#/components/schemas/Trigger"}}
            createdBy: {type: string}
            createdAt: {type: string, format: date-time}
            updatedAt: {type: string, format: date-time}
            migrated: {type: boolean}
    TriggerGroupInput:
      type: object
      required: [name, enabled]
      properties:
        name: {type: string, minLength: 1, maxLength: 120}
        description: {type: string, maxLength: 500}
        enabled: {type: boolean}
        expectedRevision: {type: integer, minimum: 0}
    Trigger:
      allOf:
        - $ref: "#/components/schemas/TriggerInput"
        - type: object
          required: [id, revision, createdBy, createdAt, updatedAt, migrated]
          properties:
            id: {type: string, format: uuid}
            revision: {type: integer, minimum: 0}
            createdBy: {type: string}
            createdAt: {type: string, format: date-time}
            updatedAt: {type: string, format: date-time}
            migrated: {type: boolean}
    TriggerInput:
      type: object
      required: [groupId, name, enabled, mode]
      properties:
        groupId: {type: string, format: uuid}
        name: {type: string, minLength: 1, maxLength: 120}
        description: {type: string, maxLength: 500}
        enabled: {type: boolean}
        mode: {type: string, enum: [visual, code]}
        event: {$ref: "#/components/schemas/TriggerEvent"}
        conditionMode: {type: string, enum: [all, any]}
        conditions:
          type: array
          items: {$ref: "#/components/schemas/TriggerCondition"}
        actions:
          type: array
          minItems: 1
          description: Root action nodes. Action and condition trees may be nested without an artificial authoring limit.
          items: {$ref: "#/components/schemas/TriggerAction"}
        script: {type: string, maxLength: 65536}
        expectedRevision: {type: integer, minimum: 0}
      oneOf:
        - title: Visual trigger
          required: [event, conditionMode, conditions, actions]
          properties:
            mode: {const: visual}
        - title: XFE Script trigger
          required: [script]
          properties:
            mode: {const: code}
    TriggerEvent:
      type: object
      required: [type, configuration]
      properties:
        type: {type: string, minLength: 1, maxLength: 80}
        configuration:
          type: object
          maxProperties: 16
          description: schedule.daily uses time and timezone. schedule.interval accepts legacy total seconds or hours/minutes/seconds components, normalizes them to total seconds (1..31536000), and aligns execution to server-local wall-clock boundaries. player.command_trigger uses command plus an optional legacy whitespace-separated arguments declaration. protection.* events use threshold and cooldownSeconds; count-based item/mob/entity events also accept scope=dimension|chunk, mod_entity_overflow accepts namespace, and slow_tick accepts consecutive.
          additionalProperties: {type: string, maxLength: 4096}
        arguments:
          type: array
          description: Recursive Brigadier alternatives for player.command_trigger. Siblings are alternatives and children form the next input level.
          items: {$ref: "#/components/schemas/TriggerCommandArgument"}
        variables:
          type: array
          description: Strongly typed trigger-local or server-global state declarations. Missing visibility/storage fields migrate as trigger-local trigger storage.
          items: {$ref: "#/components/schemas/TriggerStateVariable"}
    TriggerCommandArgument:
      type: object
      required: [name, type, literal, optional, errorMessage, minimum, maximum, suggestions, children]
      properties:
        name: {type: string, pattern: '^[a-z][a-z0-9_-]{0,31}$'}
        type: {type: string, description: A supported vanilla Brigadier argument family or literal.}
        literal: {type: string, maxLength: 128}
        optional: {type: boolean}
        errorMessage: {type: string, maxLength: 256}
        minimum: {type: string}
        maximum: {type: string}
        suggestions: {type: array, maxItems: 64, items: {type: string, minLength: 1, maxLength: 128}}
        children: {type: array, items: {$ref: "#/components/schemas/TriggerCommandArgument"}}
    TriggerStateVariable:
      type: object
      required: [name, type, initialValue]
      properties:
        name: {type: string, pattern: '^[A-Za-z_][A-Za-z0-9_-]{0,47}$'}
        type:
          type: string
          description: Scalar type, legacy list, or recursively nested array<T>/dictionary<K,V>; dictionary keys must be scalar.
        initialValue:
          type: string
          maxLength: 65536
          description: Scalars use their text form; arrays and dictionaries use strictly typed JSON.
        visibility: {type: string, enum: [trigger, global], default: trigger}
        storage:
          type: string
          enum: [server, player, dimension, trigger]
          description: Selects one shared value, a value per triggering player, per dimension, or per executing trigger.
    TriggerCondition:
      type: object
      required: [field, operator, value]
      properties:
        field: {type: string, minLength: 1, maxLength: 120}
        operator: {type: string, enum: [eq, neq, contains, not_contains, starts_with, not_starts_with, ends_with, not_ends_with, matches, not_matches, gt, gte, lt, lte, between, not_between, in, not_in, empty, not_empty, 'true', 'false', exists, not_exists]}
        value: {type: string, maxLength: 4096}
    TriggerAction:
      type: object
      required: [type, parameters]
      description: An executable action, or a structural condition whose children run only when its field/operator/value passes. Only condition nodes may have children; authoring does not impose an artificial node-count or nesting-depth limit.
      properties:
        type: {type: string, enum: [condition, send_player, broadcast, title, actionbar, sound, server_command, player_command, kick, teleport, give_item, clear_inventory, set_gamemode, add_effect, remove_effects, heal, feed, set_time, set_weather, whitelist_add, whitelist_remove, ban, pardon, log, variable, wait, run_trigger, open_menu, close_menu, economy_deposit, economy_withdraw, economy_set_balance, economy_transfer, economy_deposit_player, economy_withdraw_player, economy_set_player_balance, economy_transfer_players]}
        parameters:
          type: object
          maxProperties: 16
          description: Condition nodes use field, operator, and value. Command actions accept showFeedback=true|false and default to false. Rich message envelopes are limited to 256 segments and 8192 visible characters and are validated on save.
          additionalProperties: {type: string, maxLength: 65536}
        children:
          type: array
          description: Non-empty for condition nodes; omitted or empty for executable actions.
          items: {$ref: "#/components/schemas/TriggerAction"}
    MenuWorkspace:
      type: object
      required: [menus, totalMenus]
      properties:
        menus: {type: array, maxItems: 512, items: {$ref: "#/components/schemas/Menu"}}
        totalMenus: {type: integer, minimum: 0, maximum: 512}
    MenuSaveResult:
      type: object
      required: [menu, workspace]
      properties:
        menu: {$ref: "#/components/schemas/Menu"}
        workspace: {$ref: "#/components/schemas/MenuWorkspace"}
    Menu:
      allOf:
        - $ref: "#/components/schemas/MenuInput"
        - type: object
          required: [id, revision, createdBy, createdAt, updatedAt]
          properties:
            id: {type: string, format: uuid}
            revision: {type: integer, minimum: 0}
            createdBy: {type: string}
            createdAt: {type: string, format: date-time}
            updatedAt: {type: string, format: date-time}
    MenuInput:
      type: object
      required: [name, width, height, popupPosition, backgroundColor, controls]
      properties:
        name: {type: string, minLength: 1, maxLength: 96}
        description: {type: string, maxLength: 1024}
        width: {type: integer, minimum: 120, maximum: 1920}
        height: {type: integer, minimum: 80, maximum: 1080}
        popupPosition: {type: string, enum: [center, top, bottom, left, right, top_left, top_right, bottom_left, bottom_right, custom]}
        offsetX: {type: integer, minimum: -8192, maximum: 8192}
        offsetY: {type: integer, minimum: -8192, maximum: 8192}
        pauseGame: {type: boolean, default: false}
        backgroundColor: {type: string, pattern: '^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$'}
        controls: {type: array, maxItems: 128, items: {$ref: "#/components/schemas/MenuControl"}}
    MenuControl:
      type: object
      required: [id, type, x, y, width, height, zIndex, visibleWhen, properties, events]
      properties:
        id: {type: string, pattern: '^[A-Za-z_][A-Za-z0-9_.-]{0,63}$'}
        type: {type: string, enum: [text, rich_text, button, icon_button, image, panel, border, progress, input, checkbox, slider, item_slot, separator, spacer]}
        x: {type: integer, minimum: 0}
        y: {type: integer, minimum: 0}
        width: {type: integer, minimum: 1, maximum: 1920}
        height: {type: integer, minimum: 1, maximum: 1080}
        zIndex: {type: integer, minimum: -1000, maximum: 1000}
        visibleWhen: {type: string, maxLength: 512}
        properties:
          type: object
          maxProperties: 48
          additionalProperties: {type: string, maxLength: 16384}
        events: {type: array, maxItems: 8, items: {$ref: "#/components/schemas/MenuControlEvent"}}
    MenuControlEvent:
      type: object
      required: [event, triggerId, submenuId, closeMenu]
      properties:
        event: {type: string, enum: [click, double_click, change, submit, toggle, hover, focus, blur]}
        triggerId: {type: string, description: "Empty, or UUID of a menu.control trigger."}
        submenuId: {type: string, description: "Empty, or UUID of an existing acyclic submenu."}
        closeMenu: {type: boolean}
    MenuCatalog:
      type: object
      required: [controlTypes, popupPositions, controlEvents, triggers]
      properties:
        controlTypes: {type: array, items: {type: string}}
        popupPositions: {type: array, items: {type: string}}
        controlEvents: {type: array, items: {type: string}}
        triggers:
          type: array
          items:
            type: object
            required: [id, name, enabled]
            properties:
              id: {type: string, format: uuid}
              name: {type: string}
              enabled: {type: boolean}
    MenuImage:
      type: object
      required: [reference, name, type, source]
      properties:
        id: {type: string, format: uuid}
        reference: {type: string, description: 'uploaded:, texture:, item:, or block: reference.'}
        name: {type: string}
        type: {type: string, enum: [uploaded, texture, item, block]}
        source: {type: string}
        mimeType: {type: string, enum: [image/png, image/jpeg]}
        width: {type: integer, minimum: 1, maximum: 1024}
        height: {type: integer, minimum: 1, maximum: 1024}
        sha256: {type: string, pattern: '^[0-9a-f]{64}$'}
        dataUrl: {type: string}
    EconomyAmount:
      type: string
      pattern: '^-?(0|[1-9][0-9]*)(\.[0-9]+)?$'
      description: Exact base-10 amount. Floating-point JSON numbers are intentionally not accepted.
    CurrencyFields:
      type: object
      required: [code, name, symbol, icon, fractionDigits, initialBalance, minimumBalance, maximumBalance, playerTransferAllowed, enabled, primary]
      properties:
        code: {type: string, pattern: '^[a-z][a-z0-9_]{0,31}$'}
        name: {type: string, minLength: 1, maxLength: 64}
        symbol: {type: string, minLength: 1, maxLength: 16}
        icon: {type: string, description: 'uploaded:, texture:, item:, or block: reference.'}
        fractionDigits: {type: integer, minimum: 0, maximum: 6}
        initialBalance: {$ref: "#/components/schemas/EconomyAmount"}
        minimumBalance: {$ref: "#/components/schemas/EconomyAmount"}
        maximumBalance: {$ref: "#/components/schemas/EconomyAmount"}
        playerTransferAllowed: {type: boolean, description: Allows player-initiated command payments; audited management and trigger transfers remain available.}
        enabled: {type: boolean}
        primary: {type: boolean}
    CurrencyInput:
      allOf:
        - $ref: "#/components/schemas/CurrencyFields"
        - type: object
          required: [reason]
          properties:
            reason: {type: string, minLength: 1, maxLength: 512}
    Currency:
      allOf:
        - $ref: "#/components/schemas/CurrencyFields"
        - type: object
          required: [id, revision, createdBy, createdAt, updatedAt]
          properties:
            id: {type: string, format: uuid}
            revision: {type: integer, minimum: 0}
            createdBy: {type: string}
            createdAt: {type: string, format: date-time}
            updatedAt: {type: string, format: date-time}
    EconomyBalance:
      type: object
      required: [currencyId, currencyCode, currencyName, symbol, icon, fractionDigits, amount, revision, currencyEnabled, updatedAt]
      properties:
        currencyId: {type: string, format: uuid}
        currencyCode: {type: string}
        currencyName: {type: string}
        symbol: {type: string}
        icon: {type: string}
        fractionDigits: {type: integer, minimum: 0, maximum: 6}
        amount: {$ref: "#/components/schemas/EconomyAmount"}
        revision: {type: integer, minimum: 0}
        currencyEnabled: {type: boolean}
        updatedAt: {type: string, format: date-time}
    EconomyAccount:
      type: object
      required: [playerId, playerName, balances, updatedAt]
      properties:
        playerId: {type: string, format: uuid}
        playerName: {type: string}
        balances: {type: array, maxItems: 64, items: {$ref: "#/components/schemas/EconomyBalance"}}
        updatedAt: {type: string, format: date-time}
    EconomyTransaction:
      type: object
      required: [id, currencyId, currencyCode, kind, sourcePlayerName, targetPlayerName, amount, sourceBalanceBefore, sourceBalanceAfter, targetBalanceBefore, targetBalanceAfter, reason, actor, origin, correlationId, createdAt]
      properties:
        id: {type: string, format: uuid}
        currencyId: {type: string, format: uuid}
        currencyCode: {type: string}
        kind: {type: string, enum: [initial, deposit, withdraw, set, transfer]}
        sourcePlayerId: {type: string, format: uuid}
        sourcePlayerName: {type: string}
        targetPlayerId: {type: string, format: uuid}
        targetPlayerName: {type: string}
        amount: {$ref: "#/components/schemas/EconomyAmount"}
        sourceBalanceBefore: {type: string}
        sourceBalanceAfter: {type: string}
        targetBalanceBefore: {type: string}
        targetBalanceAfter: {type: string}
        reason: {type: string}
        actor: {type: string}
        origin: {type: string, enum: [system, web, command, trigger]}
        correlationId: {type: string}
        createdAt: {type: string, format: date-time}
    EconomyCurrencySummary:
      type: object
      required: [currencyId, code, name, symbol, icon, fractionDigits, enabled, primary, accountCount, totalSupply, minimumBalance, maximumBalance, transactionCount, transferVolume24h]
      properties:
        currencyId: {type: string, format: uuid}
        code: {type: string}
        name: {type: string}
        symbol: {type: string}
        icon: {type: string}
        fractionDigits: {type: integer}
        enabled: {type: boolean}
        primary: {type: boolean}
        accountCount: {type: integer, minimum: 0}
        totalSupply: {$ref: "#/components/schemas/EconomyAmount"}
        minimumBalance: {$ref: "#/components/schemas/EconomyAmount"}
        maximumBalance: {$ref: "#/components/schemas/EconomyAmount"}
        transactionCount: {type: integer, minimum: 0}
        transferVolume24h: {$ref: "#/components/schemas/EconomyAmount"}
    EconomyWorkspace:
      type: object
      required: [currencies, overview]
      properties:
        currencies: {type: array, maxItems: 64, items: {$ref: "#/components/schemas/Currency"}}
        overview:
          type: object
          required: [currencies, observedAt]
          properties:
            currencies: {type: array, maxItems: 64, items: {$ref: "#/components/schemas/EconomyCurrencySummary"}}
            observedAt: {type: string, format: date-time}
    EconomyChange:
      type: object
      required: [currency, transaction]
      properties:
        currency: {$ref: "#/components/schemas/Currency"}
        transaction: {$ref: "#/components/schemas/EconomyTransaction"}
        sourceBalance: {$ref: "#/components/schemas/EconomyBalance"}
        targetBalance: {$ref: "#/components/schemas/EconomyBalance"}
    ChunkPosition:
      type: object
      required: [x, z]
      properties:
        x: {type: integer}
        z: {type: integer}
    Claim:
      type: object
      required: [id, dimension, ownerId, chunks, members, flags, revision]
      properties:
        id: {type: string, format: uuid}
        name: {type: string}
        dimension: {type: string}
        ownerId: {type: string, format: uuid}
        chunks: {type: array, items: {$ref: "#/components/schemas/ChunkPosition"}}
        members:
          type: object
          additionalProperties: {type: string, enum: [manager, member, trusted]}
        flags:
          type: object
          additionalProperties: {type: boolean}
        revision: {type: integer, minimum: 0}
    CreateClaim:
      type: object
      required: [name, dimension, chunks]
      properties:
        name: {type: string, minLength: 1, maxLength: 64}
        dimension: {type: string}
        chunks: {type: array, minItems: 1, items: {$ref: "#/components/schemas/ChunkPosition"}}
        adminOverride: {type: boolean, default: false}
    ClaimPatch:
      type: object
      required: [expectedRevision, reason]
      properties:
        expectedRevision: {type: integer, minimum: 0}
        name: {type: string, minLength: 1, maxLength: 64}
        chunks: {type: array, minItems: 1, items: {$ref: "#/components/schemas/ChunkPosition"}}
        members: {type: object, additionalProperties: {type: string}}
        flags: {type: object, additionalProperties: {type: boolean}}
        transferTo: {type: string, format: uuid}
        transferConfirmation: {type: string}
        reason: {type: string, minLength: 1, maxLength: 500}
    ClaimPage:
      type: object
      required: [items, page]
      properties:
        items: {type: array, items: {$ref: "#/components/schemas/Claim"}}
        page: {$ref: "#/components/schemas/PageMetadata"}
    WorldChange:
      type: object
      required: [id, timestamp, tick, dimension, position, action, resourceId, restorable]
      properties:
        id: {type: string, format: uuid}
        timestamp: {type: string, format: date-time}
        tick: {type: integer}
        dimension: {type: string}
        position: {$ref: "#/components/schemas/Position"}
        actorId: {type: [string, "null"], format: uuid}
        cause: {type: string}
        action: {type: string}
        resourceId: {type: string}
        modFingerprint: {type: [string, "null"]}
        restorable: {type: boolean}
        incompatibility: {type: [string, "null"]}
        transactionId: {type: string, format: uuid}
    WorldChangePage:
      type: object
      required: [items, page, gaps]
      properties:
        items: {type: array, items: {$ref: "#/components/schemas/WorldChange"}}
        page: {$ref: "#/components/schemas/PageMetadata"}
        gaps:
          type: array
          items:
            type: object
            properties:
              from: {type: string, format: date-time}
              to: {type: string, format: date-time}
              reason: {type: string}
    RollbackSelection:
      type: object
      required: [from, to, reason]
      properties:
        from: {type: string, format: date-time}
        to: {type: string, format: date-time}
        actorId: {type: [string, "null"], format: uuid}
        dimension: {type: [string, "null"]}
        center: {$ref: "#/components/schemas/Position"}
        radius: {type: integer, minimum: 0, maximum: 2048}
        claimId: {type: [string, "null"], format: uuid}
        actions: {type: array, items: {type: string}}
        includeEntitiesAndItems: {type: boolean, default: false}
        reason: {type: string, minLength: 1, maxLength: 500}
    RollbackPreview:
      type: object
      required: [id, watermark, expiresAt, selected, applicable, conflicts, unloadedChunks, gaps, incompatible, confirmationToken]
      properties:
        id: {type: string, format: uuid}
        watermark: {type: integer, format: int64, minimum: 0}
        expiresAt: {type: string, format: date-time}
        selected: {type: integer, minimum: 0}
        applicable: {type: integer, minimum: 0}
        conflicts: {type: integer, minimum: 0}
        unloadedChunks: {type: integer, minimum: 0}
        gaps: {type: integer, minimum: 0}
        incompatible: {type: integer, minimum: 0}
        estimatedChunkLoads: {type: integer, minimum: 0, maximum: 128}
        confirmationToken: {type: string}
