openapi: 3.0.4
info:
  title: MyHoreca Licensing Runtime API
  description: >-
    Public Runtime API surface for integrators (activate / validate / leases / devices / JWKS / offline / floating /
    config). Management admin OpenAPI is not published here. Servers use a placeholder host — replace with your Runtime
    base URL. No credentials or secrets are embedded.
  version: 1.0.0
  x-myhoreca-public-surface: runtime
  x-myhoreca-wave: '32.5'
paths:
  /health:
    get:
      tags:
        - Health
      operationId: GetHealth
      responses:
        '200':
          description: OK
  /ready:
    get:
      tags:
        - Health
      operationId: GetReady
      responses:
        '200':
          description: OK
  /api/v1/device-registrations:
    post:
      tags:
        - RuntimeDevices
      operationId: RegisterDevice
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RegisterDeviceRequest'
        required: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RegisterDeviceResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Too Many Requests
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/jwks:
    get:
      tags:
        - RuntimeKeys
      operationId: GetRuntimeJwks
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JwksResponse'
  /api/v1/offline/responses:
    post:
      tags:
        - RuntimeOffline
      operationId: RuntimeIssueOfflineResponse
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RuntimeIssueOfflineResponseRequest'
        required: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RuntimeIssueOfflineResponseResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Service Unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/config/fetch:
    post:
      tags:
        - RuntimeConfig
      operationId: FetchRuntimeConfig
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RuntimeConfigFetchBody'
        required: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RuntimeConfigFetchResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Service Unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/licenses/activate:
    post:
      tags:
        - RuntimeLicenses
      operationId: ActivateLicense
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ActivateLicenseRequest'
        required: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ActivateLicenseResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Too Many Requests
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Service Unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/licenses/validate:
    post:
      tags:
        - RuntimeLicenses
      operationId: ValidateLicense
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ValidateLicenseRequest'
        required: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidateLicenseResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Too Many Requests
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/leases/refresh:
    post:
      tags:
        - RuntimeLeases
      operationId: RefreshLease
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefreshLeaseRequest'
        required: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RefreshLeaseResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Too Many Requests
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Service Unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/floating/checkout:
    post:
      tags:
        - RuntimeFloating
      operationId: RuntimeFloatingCheckout
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RuntimeFloatingMutationRequest'
        required: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RuntimeFloatingSeatResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Service Unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/floating/heartbeat:
    post:
      tags:
        - RuntimeFloating
      operationId: RuntimeFloatingHeartbeat
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RuntimeFloatingMutationRequest'
        required: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RuntimeFloatingSeatResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/floating/release:
    post:
      tags:
        - RuntimeFloating
      operationId: RuntimeFloatingRelease
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RuntimeFloatingMutationRequest'
        required: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RuntimeFloatingSeatResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/v1/floating/borrow:
    post:
      tags:
        - RuntimeFloating
      operationId: RuntimeFloatingBorrow
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RuntimeFloatingMutationRequest'
        required: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RuntimeFloatingSeatResponse'
        '400':
          description: Bad Request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Not Found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Conflict
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Service Unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /__foundation/validate:
    post:
      tags:
        - FoundationValidation
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FoundationValidateRequest'
          text/json:
            schema:
              $ref: '#/components/schemas/FoundationValidateRequest'
          application/*+json:
            schema:
              $ref: '#/components/schemas/FoundationValidateRequest'
        required: true
      responses:
        '200':
          description: OK
          content:
            text/plain:
              schema:
                $ref: '#/components/schemas/FoundationValidateRequest'
            application/json:
              schema:
                $ref: '#/components/schemas/FoundationValidateRequest'
            text/json:
              schema:
                $ref: '#/components/schemas/FoundationValidateRequest'
components:
  schemas:
    ActivateLicenseRequest:
      type: object
      properties:
        licenseId:
          type: string
          format: uuid
        deviceId:
          type: string
          format: uuid
          nullable: true
        fingerprintDigest:
          type: string
          nullable: true
    ActivateLicenseResponse:
      type: object
      properties:
        leaseId:
          type: string
          format: uuid
          nullable: true
        leaseToken:
          type: string
          nullable: true
          description: >-
            Compact JWS (MH-LEASE-V1). After verify, payload may include `customData` (ADR-0026 fixed slots: string1/2,
            bool1/2, number1..3) copied from Management SoR. See docs/contracts/license-custom-data-v1.md. No Runtime
            CRUD for custom data.
        status:
          type: string
    ErrorResponse:
      required:
        - code
        - message
        - correlationId
      type: object
      properties:
        code:
          type: string
        message:
          type: string
        correlationId:
          type: string
        details:
          type: object
          nullable: true
        documentationUrl:
          type: string
          nullable: true
      description: Typed public API error body (`docs/contracts/error-contract.md`).
    FoundationValidateRequest:
      required:
        - name
      type: object
      properties:
        name:
          minLength: 1
          type: string
      description: Request used only to exercise invalid-model → error-contract mapping.
    JwksResponse:
      type: object
      properties:
        keys:
          type: array
          items:
            type: object
    QuotaDecisionEvidence:
      type: object
      properties:
        budgetProjectionId:
          type: string
          format: uuid
        projectionVersion:
          pattern: ^-?(?:0|[1-9]\d*)$
          format: int64
        meterCode:
          type: string
        requestedUnits:
          pattern: ^-?(?:0|[1-9]\d*)(?:\.\d+)?$
          format: double
        grantedUnits:
          pattern: ^-?(?:0|[1-9]\d*)(?:\.\d+)?$
          format: double
        consumedUnits:
          pattern: ^-?(?:0|[1-9]\d*)(?:\.\d+)?$
          format: double
        enforcementMode:
          type: string
    RefreshLeaseRequest:
      type: object
      properties:
        licenseId:
          type: string
          format: uuid
        deviceId:
          type: string
          format: uuid
          nullable: true
        leaseToken:
          type: string
    RefreshLeaseResponse:
      type: object
      properties:
        leaseId:
          type: string
          format: uuid
          nullable: true
        leaseToken:
          type: string
          nullable: true
        status:
          type: string
    RegisterDeviceRequest:
      type: object
      properties:
        licenseId:
          type: string
          format: uuid
        deviceIdentityDigest:
          type: string
    RegisterDeviceResponse:
      type: object
      properties:
        deviceId:
          type: string
          format: uuid
          nullable: true
        status:
          type: string
    RuntimeConfigFetchBody:
      type: object
      properties:
        deviceId:
          type: string
          format: uuid
        documentId:
          type: string
          format: uuid
          nullable: true
        code:
          type: string
          nullable: true
        recipientKid:
          type: string
          nullable: true
    RuntimeConfigFetchResponse:
      required:
        - publicConfigJws
      type: object
      properties:
        publicConfigJws:
          type: string
        secretBundleJwe:
          type: string
          nullable: true
    RuntimeFloatingMutationRequest:
      type: object
      properties:
        licenseId:
          type: string
          format: uuid
        seatId:
          type: string
          format: uuid
        scopeKey:
          type: string
          nullable: true
        featureCode:
          type: string
          nullable: true
        holderDeviceId:
          type: string
          format: uuid
          nullable: true
        holderBindingHash:
          type: string
          nullable: true
        floatingLeaseDurationSeconds:
          pattern: ^-?(?:0|[1-9]\d*)$
          format: int32
        heartbeatIntervalSeconds:
          pattern: ^-?(?:0|[1-9]\d*)$
          format: int32
        borrowDurationSeconds:
          pattern: ^-?(?:0|[1-9]\d*)$
          format: int32
    RuntimeFloatingSeatResponse:
      type: object
      properties:
        seatId:
          type: string
          format: uuid
        licenseId:
          type: string
          format: uuid
        status:
          type: string
        expiresAt:
          type: string
          format: date-time
        heartbeatIntervalSeconds:
          pattern: ^-?(?:0|[1-9]\d*)$
          format: int32
        alreadyApplied:
          type: boolean
        correlationId:
          type: string
          nullable: true
        degraded:
          type: boolean
        scopeKey:
          type: string
          nullable: true
        featureCode:
          type: string
          nullable: true
    RuntimeIssueOfflineResponseRequest:
      type: object
      properties:
        requestCompactJws:
          type: string
        expectedRequestAudience:
          type: string
        iss:
          type: string
        aud:
          type: string
        signingEnvironment:
          type: string
        decision:
          type: string
        reasonCode:
          type: string
        lifetimeSeconds:
          pattern: ^-?(?:0|[1-9]\d*)$
          format: int32
        entitlementsDigest:
          type: string
          nullable: true
        leaseRef:
          type: string
          nullable: true
        kid:
          type: string
          nullable: true
    RuntimeIssueOfflineResponseResponse:
      type: object
      properties:
        compactJws:
          type: string
        artifactId:
          type: string
          format: uuid
          nullable: true
        correlationId:
          type: string
          nullable: true
    ValidateLicenseRequest:
      type: object
      properties:
        licenseId:
          type: string
          format: uuid
        deviceId:
          type: string
          format: uuid
          nullable: true
        leaseToken:
          type: string
          nullable: true
        meterCode:
          type: string
          nullable: true
        requestedUnits:
          pattern: ^-?(?:0|[1-9]\d*)(?:\.\d+)?$
          format: double
    ValidateLicenseResponse:
      type: object
      properties:
        decision:
          type: string
        reasonCode:
          type: string
        degraded:
          type: boolean
        correlationId:
          type: string
          nullable: true
        quota:
          oneOf:
            - enum:
                - null
            - $ref: '#/components/schemas/QuotaDecisionEvidence'
tags:
  - name: Health
  - name: RuntimeDevices
  - name: RuntimeKeys
  - name: RuntimeOffline
  - name: RuntimeConfig
  - name: RuntimeLicenses
  - name: RuntimeLeases
  - name: RuntimeFloating
  - name: FoundationValidation
servers:
  - url: https://runtime.example.com
    description: Placeholder Runtime base URL
