openapi: 3.0.3
info:
  title: Xapps Gateway API
  description: API for the Xapps Marketplace, Widget Platform, and Request Gateway.
  version: 1.0.0
servers:
  - url: http://localhost:3000
    description: Local development server

paths:
  /health:
    get:
      summary: Health check
      tags: [System]
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string }

  /v1/xapps:
    get:
      summary: List catalog xapps
      tags: [Catalog]
      description: |
        Returns a list of available Xapps in the catalog.

        Auth modes:
        - When `PUBLIC_CATALOG_ENABLED=true`, this endpoint may be called without authentication.
          In public mode, only active Xapps with `visibility: "public"` are returned, and sensitive
          fields (like internal configuration or private metadata) are suppressed.
        - Otherwise requires `X-API-Key` or `Authorization: Bearer <userSession>`.
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
        - {}
      responses:
        "200":
          description: Xapps list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/Xapp" }

  /v1/xapps/{xappId}:
    get:
      summary: Get xapp details (includes tools + widgets)
      tags: [Catalog]
      description: |
        Catalog detail.

        Auth modes:
        - When `PUBLIC_CATALOG_ENABLED=true`, this endpoint may be called without authentication.
          In public mode, sensitive fields are suppressed (e.g. full manifest/tools/widgets).
        - Otherwise requires `X-API-Key` or `Authorization: Bearer <userSession>`.
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
        - {}
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Xapp details
          content:
            application/json:
              schema:
                type: object
                properties:
                  xapp: { $ref: "#/components/schemas/Xapp" }
                  version: { $ref: "#/components/schemas/XappVersion" }
                  manifest:
                    type: object
                    nullable: true
                    additionalProperties: true
                  tools:
                    type: array
                    items: { $ref: "#/components/schemas/XappTool" }
                  widgets:
                    type: array
                    items: { $ref: "#/components/schemas/XappWidget" }
                  operational_surfaces:
                    $ref: "#/components/schemas/OperationalSurfacesDescriptor"
                  usage_credit_summary:
                    type: object
                    nullable: true
                    properties:
                      available_count: { type: integer }
                      available_session_backed_count: { type: integer }
                      available_ledger_only_count: { type: integer }
                      reserved_count: { type: integer }
                      reserved_active_count: { type: integer }
                      reserved_stale_count: { type: integer }
                      consumed_count: { type: integer }
                      updated_at:
                        type: [string, "null"]
                      by_tool:
                        type: array
                        items:
                          type: object
                          properties:
                            tool_name: { type: string }
                            available_count: { type: integer }
                            available_session_backed_count: { type: integer }
                            available_ledger_only_count: { type: integer }
                            reserved_count: { type: integer }
                            reserved_active_count: { type: integer }
                            reserved_stale_count: { type: integer }
                            consumed_count: { type: integer }

  /v1/xapps/{xappId}/monetization:
    get:
      summary: Get monetization state for an xapp
      tags: [Catalog]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      description: |
        Returns the published monetization catalog for the xapp, including resolved paywalls.

        Current gateway targeting support on this lane enforces:
        - offering/paywall `targeting_rules`
        - price `country_rules`
        - price `trial_policy`
        - price `intro_policy`

        Current enforced subset:
        - locale include/exclude
        - country include/exclude
        - scope requirements:
          - `require_subject`
          - `require_installation`
          - `require_realm`
        - first-time-only free trials for `subscription_plan` / `hybrid_plan`
        - first-time-only intro discounts for `subscription_plan` / `hybrid_plan`
        - `trial_policy` wins over `intro_policy` when both qualify
        - zero-cost qualified lanes can finalize without an external payment session
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: placement
          in: query
          required: false
          schema: { type: string }
        - name: locale
          in: query
          required: false
          schema: { type: string }
        - name: country
          in: query
          required: false
          schema: { type: string }
        - name: subject_id
          in: query
          required: false
          schema: { type: string }
        - name: subjectId
          in: query
          required: false
          schema: { type: string }
        - name: installation_id
          in: query
          required: false
          schema: { type: string }
        - name: installationId
          in: query
          required: false
          schema: { type: string }
        - name: realm_ref
          in: query
          required: false
          schema: { type: string }
        - name: realmRef
          in: query
          required: false
          schema: { type: string }
      responses:
        "200":
          description: Monetization catalog for the xapp, including active offerings and additive paywall projections built from the published manifest.
          content:
            application/json:
              schema: { type: object }

  /v1/xapps/{xappId}/monetization/usage-policies/{toolName}:
    get:
      summary: Get XMS usage policy for an xapp tool
      tags: [Catalog]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      description: |
        Returns the published manifest-owned XMS usage policy for one tool.
        This is the reusable authority lane for request-time credit consumption,
        analogous to pay-by-request tool price policy: the tool payload must not
        decide its own credit cost.
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: toolName
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Usage policy for the requested tool.
          content:
            application/json:
              schema:
                type: object
                properties:
                  xapp_id: { type: string }
                  version_id: { type: string }
                  usage_policy:
                    type: object
                    properties:
                      tool_name: { type: string }
                      unit:
                        type: [string, "null"]
                      credit_cost:
                        type: number
                      status: { type: string }
                      metadata:
                        type: [object, "null"]

  /v1/xapps/{xappId}/monetization/paywalls:
    get:
      summary: List additive paywall projections for an xapp
      tags: [Catalog]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      description: |
        Lists published paywalls resolved against the active monetization catalog for the xapp.

        Current gateway targeting support on this lane enforces paywall `targeting_rules`
        for locale/country/scope matching, and the resolved package views carry the
        same `trial_policy` / `intro_policy` metadata used by purchase-intent preparation.
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: placement
          in: query
          required: false
          schema: { type: string }
        - name: locale
          in: query
          required: false
          schema: { type: string }
        - name: country
          in: query
          required: false
          schema: { type: string }
        - name: subject_id
          in: query
          required: false
          schema: { type: string }
        - name: subjectId
          in: query
          required: false
          schema: { type: string }
        - name: installation_id
          in: query
          required: false
          schema: { type: string }
        - name: installationId
          in: query
          required: false
          schema: { type: string }
        - name: realm_ref
          in: query
          required: false
          schema: { type: string }
        - name: realmRef
          in: query
          required: false
          schema: { type: string }
      responses:
        "200":
          description: Published paywall definitions resolved against the active offering/package catalog for the xapp.
          content:
            application/json:
              schema: { type: object }

  /v1/xapps/{xappId}/monetization/access:
    get:
      summary: Get access projection for an xapp scope
      tags: [Catalog]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: subjectId
          in: query
          required: false
          schema: { type: string }
        - name: installationId
          in: query
          required: false
          schema: { type: string }
        - name: realmRef
          in: query
          required: false
          schema: { type: string }
      responses:
        "200":
          description: Access projection for the selected xapp scope
          content:
            application/json:
              schema: { type: object }

  /v1/xapps/{xappId}/monetization/current-subscription:
    get:
      summary: Get current subscription state for an xapp scope
      tags: [Catalog]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: subjectId
          in: query
          required: false
          schema: { type: string }
        - name: installationId
          in: query
          required: false
          schema: { type: string }
        - name: realmRef
          in: query
          required: false
          schema: { type: string }
      responses:
        "200":
          description: Current subscription and access projection for the xapp scope
          content:
            application/json:
              schema: { type: object }

  /v1/xapps/{xappId}/monetization/entitlements:
    get:
      summary: List entitlement records for an xapp scope
      tags: [Catalog]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Entitlement records for the selected xapp scope, including additive durable unlocks when present.
          content:
            application/json:
              schema: { type: object }

  /v1/xapps/{xappId}/monetization/purchase-intents/prepare:
    post:
      summary: Prepare a purchase intent for an xapp
      tags: [Catalog]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      description: |
        Prepares an XMS purchase intent from the published xapp monetization catalog.

        Current gateway policy on this lane enforces:
        - offering `targeting_rules`
        - paywall `targeting_rules`
        - price `country_rules`
        - price `trial_policy`
        - price `intro_policy`

        `locale` and `country` may be supplied in the request body to evaluate those rules.
        The current pricing-policy subset supports first-time free trials and intro discounts
        for `subscription_plan` / `hybrid_plan`, with `trial_policy` taking precedence.
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object }
      responses:
        "200":
          description: Prepared purchase intent for the selected package and price
          content:
            application/json:
              schema: { type: object }

  /v1/xapps/{xappId}/monetization/purchase-intents/{intentId}:
    get:
      summary: Get a purchase intent for an xapp
      tags: [Catalog]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: intentId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Purchase intent state for the selected xapp scope
          content:
            application/json:
              schema: { type: object }

  /v1/xapps/{xappId}/monetization/purchase-intents/{intentId}/transactions:
    post:
      summary: Create a purchase transaction for an xapp intent
      tags: [Catalog]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: intentId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Created purchase transaction for the selected intent
          content:
            application/json:
              schema: { type: object }
    get:
      summary: List purchase transactions for an xapp intent
      tags: [Catalog]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: intentId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Purchase transactions for the selected intent
          content:
            application/json:
              schema: { type: object }

  /v1/xapps/{xappId}/monetization/purchase-intents/{intentId}/payment-session:
    post:
      summary: Create a hosted payment session for an xapp purchase intent
      tags: [Catalog]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: intentId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Hosted payment session for the selected purchase intent
          content:
            application/json:
              schema: { type: object }

  /v1/xapps/{xappId}/monetization/purchase-intents/{intentId}/payment-session/reconcile:
    post:
      summary: Reconcile a hosted payment session for an xapp purchase intent
      tags: [Catalog]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: intentId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Reconciled payment session and transaction state
          content:
            application/json:
              schema: { type: object }

  /v1/xapps/{xappId}/monetization/purchase-intents/{intentId}/issue-access:
    post:
      summary: Issue monetization access from an xapp purchase intent
      tags: [Catalog]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: intentId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Issued access projection and snapshot for the selected intent
          content:
            application/json:
              schema: { type: object }

  /v1/xapps/{xappId}/monetization/subscription-contracts/{contractId}/reconcile-payment-session:
    post:
      summary: Reconcile a payment session for an xapp subscription contract
      tags: [Catalog]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: contractId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Reconciled subscription contract state
          content:
            application/json:
              schema: { type: object }

  /v1/xapps/{xappId}/monetization/subscription-contracts/{contractId}/cancel:
    post:
      summary: Cancel an xapp subscription contract
      tags: [Catalog]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: contractId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Cancelled subscription contract state
          content:
            application/json:
              schema: { type: object }

  /v1/xapps/{xappId}/monetization/subscription-contracts/{contractId}/refresh-state:
    post:
      summary: Refresh an xapp subscription contract state
      tags: [Catalog]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: contractId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Refreshed subscription contract state
          content:
            application/json:
              schema: { type: object }

  /v1/publisher/monetization/catalog:
    get:
      summary: List publisher monetization catalog offerings
      tags: [Publisher]
      security:
        - PublisherApiKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Publisher catalog offerings
          content:
            application/json:
              schema: { type: object }

  /v1/publisher/monetization/subscriptions:
    get:
      summary: List publisher monetization subscriptions
      tags: [Publisher]
      security:
        - PublisherApiKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Publisher subscriptions
          content:
            application/json:
              schema: { type: object }

  /v1/publisher/xapps/{xappId}/monetization:
    get:
      summary: Get publisher monetization state for an xapp
      tags: [Publisher]
      security:
        - PublisherApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Publisher monetization state for the selected xapp, including catalog paywall projections for the published version.
          content:
            application/json:
              schema: { type: object }

  /v1/publisher/xapps/{xappId}/monetization/subscription-contracts/{contractId}/cancel:
    post:
      summary: Cancel a publisher xapp subscription contract
      tags: [Publisher]
      security:
        - PublisherApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: contractId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Cancelled publisher subscription contract state
          content:
            application/json:
              schema: { type: object }

  /v1/publisher/xapps/{xappId}/monetization/subscription-contracts/{contractId}/refresh-state:
    post:
      summary: Refresh a publisher xapp subscription contract state
      tags: [Publisher]
      security:
        - PublisherApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: contractId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Refreshed publisher subscription contract state
          content:
            application/json:
              schema: { type: object }

  /v1/publisher/xapps/{xappId}/monetization/subscription-contracts/{contractId}/reconcile-payment-session:
    post:
      summary: Reconcile a payment session for a publisher xapp subscription contract
      tags: [Publisher]
      security:
        - PublisherApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: contractId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Reconciled publisher subscription contract state
          content:
            application/json:
              schema: { type: object }

  /v1/publisher/xapps/{xappId}/monetization/entitlements/{entitlementId}/set-status:
    post:
      summary: Set status for a publisher xapp entitlement
      tags: [Publisher]
      security:
        - PublisherApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: entitlementId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Updated publisher entitlement state
          content:
            application/json:
              schema: { type: object }

  /v1/publisher/xapps/{xappId}/monetization/transactions/{transactionId}/reconcile-payment-session:
    post:
      summary: Reconcile a payment session for a publisher xapp transaction
      tags: [Publisher]
      security:
        - PublisherApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: transactionId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Reconciled publisher transaction state
          content:
            application/json:
              schema: { type: object }

  /v1/publisher/xapps/{xappId}/monetization/wallet-accounts/{walletAccountId}/set-status:
    post:
      summary: Set status for a publisher xapp wallet account
      tags: [Publisher]
      security:
        - PublisherApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: walletAccountId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Updated publisher wallet account state
          content:
            application/json:
              schema: { type: object }

  /v1/publisher/xapps/{xappId}/monetization/wallet-accounts/{walletAccountId}/adjust-balance:
    post:
      summary: Adjust balance for a publisher xapp wallet account
      tags: [Publisher]
      security:
        - PublisherApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: walletAccountId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                event_kind:
                  type: string
                  enum: [top_up, grant, refund, expire]
                amount:
                  type: string
                  nullable: true
                note:
                  type: string
                  nullable: true
              required: [event_kind]
      responses:
        "200":
          description: Updated publisher wallet balance state
          content:
            application/json:
              schema: { type: object }

  /v1/superadmin/monetization/catalog:
    get:
      summary: List superadmin monetization catalog offerings
      tags: [Superadmin]
      security:
        - SuperadminApiKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Superadmin catalog offerings
          content:
            application/json:
              schema: { type: object }

  /v1/superadmin/monetization/subscriptions:
    get:
      summary: List superadmin monetization subscriptions
      tags: [Superadmin]
      security:
        - SuperadminApiKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Superadmin subscriptions
          content:
            application/json:
              schema: { type: object }

  /v1/superadmin/xapps/{xappId}/monetization:
    get:
      summary: Get superadmin monetization state for an xapp
      tags: [Superadmin]
      security:
        - SuperadminApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Superadmin monetization state for the selected xapp, including catalog paywall projections for the published version.
          content:
            application/json:
              schema: { type: object }

  /v1/superadmin/xapps/{xappId}/monetization/subscription-contracts/{contractId}/refresh-state:
    post:
      summary: Refresh superadmin subscription lifecycle state for an xapp contract
      tags: [Superadmin]
      security:
        - SuperadminApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: contractId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Refreshed superadmin lifecycle state for the selected subscription contract.
          content:
            application/json:
              schema: { type: object }

  /v1/superadmin/xapps/{xappId}/monetization/subscription-contracts/{contractId}/cancel:
    post:
      summary: Cancel renewal for a superadmin-managed subscription contract
      tags: [Superadmin]
      security:
        - SuperadminApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: contractId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Cancelled renewal state for the selected superadmin subscription contract.
          content:
            application/json:
              schema: { type: object }

  /v1/superadmin/xapps/{xappId}/monetization/subscription-contracts/{contractId}/reconcile-payment-session:
    post:
      summary: Reconcile the payment session for a superadmin subscription contract
      tags: [Superadmin]
      security:
        - SuperadminApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: contractId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema: { type: object }
      responses:
        "200":
          description: Reconciled lifecycle state for the selected superadmin subscription contract.
          content:
            application/json:
              schema: { type: object }

  /v1/superadmin/xapps/{xappId}/monetization/wallet-accounts/{walletAccountId}/set-status:
    post:
      summary: Set status for a superadmin xapp wallet account
      tags: [Superadmin]
      security:
        - SuperadminApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: walletAccountId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                status:
                  type: string
                  enum: [active, suspended, closed]
              required: [status]
      responses:
        "200":
          description: Updated superadmin wallet account state.
          content:
            application/json:
              schema: { type: object }

  /v1/superadmin/xapps/{xappId}/monetization/wallet-accounts/{walletAccountId}/adjust-balance:
    post:
      summary: Adjust balance for a superadmin xapp wallet account
      tags: [Superadmin]
      security:
        - SuperadminApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: walletAccountId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                event_kind:
                  type: string
                  enum: [top_up, grant, refund, expire]
                amount:
                  type: string
                  nullable: true
                note:
                  type: string
                  nullable: true
              required: [event_kind]
      responses:
        "200":
          description: Updated superadmin wallet balance state.
          content:
            application/json:
              schema: { type: object }

  /v1/me/xapps/{xappId}/monetization:
    get:
      summary: Get the signed-in user monetization state for an xapp
      tags: [Portal]
      security:
        - BearerAuth: []
      description: |
        Returns current-user monetization state for the selected published xapp.

        Current gateway policy on this lane enforces:
        - offering/paywall `targeting_rules`
        - price `country_rules`
        - price `trial_policy`
        - price `intro_policy`

        `locale` and `country` can be supplied directly; when `country` is omitted the
        current selected subject billing profile may supply the country context. The
        current pricing-policy subset supports first-time free trials and intro discounts
        for `subscription_plan` / `hybrid_plan`, with `trial_policy` taking precedence.
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: installationId
          in: query
          required: false
          schema: { type: string }
        - name: locale
          in: query
          required: false
          schema: { type: string }
        - name: country
          in: query
          required: false
          schema: { type: string }
      responses:
        "200":
          description: Signed-in user monetization state for the selected xapp, including resolved paywall projections for the published version when present, the current subscription view, additive entitlement summaries such as durable unlocks, and normalized `current_subscription.subscription_management` hints for subject-side lifecycle actions vs operator-owned authority, including concrete management destinations when an internal operator console exists.
          content:
            application/json:
              schema: { type: object }

  /v1/me/xapps/{xappId}/monetization/subscription-contracts/{contractId}/cancel:
    post:
      summary: Cancel renewal for the signed-in user's xapp subscription contract
      tags: [Portal]
      security:
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: contractId
          in: path
          required: true
          schema: { type: string }
        - name: subjectId
          in: query
          required: false
          schema: { type: string }
        - name: installationId
          in: query
          required: false
          schema: { type: string }
      responses:
        "200":
          description: Updated current-user subscription lifecycle after cancelling renewal for the selected contract.
          content:
            application/json:
              schema: { type: object }

  /v1/me/xapps/{xappId}/monetization/subscription-contracts/{contractId}/refresh-state:
    post:
      summary: Refresh the signed-in user's xapp subscription lifecycle state
      tags: [Portal]
      security:
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: contractId
          in: path
          required: true
          schema: { type: string }
        - name: subjectId
          in: query
          required: false
          schema: { type: string }
        - name: installationId
          in: query
          required: false
          schema: { type: string }
      responses:
        "200":
          description: Updated current-user subscription lifecycle after refreshing the selected contract.
          content:
            application/json:
              schema: { type: object }

  /v1/me/xapps/{xappId}/monetization/history:
    get:
      summary: Get the signed-in user monetization history for an xapp
      tags: [Portal]
      security:
        - BearerAuth: []
      description: |
        Returns grouped current-user monetization history for the selected published xapp.

        Current history buckets include:
        - purchase intents
        - transactions
        - subscriptions
        - entitlements
        - wallet accounts
        - wallet ledger
        - access snapshots
        - correlated invoices
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: subjectId
          in: query
          required: false
          schema: { type: string }
        - name: limit
          in: query
          required: false
          schema: { type: integer, minimum: 1, maximum: 100, default: 10 }
      responses:
        "200":
          description: Signed-in user monetization history grouped for the selected xapp.
          content:
            application/json:
              schema: { type: object }

  /v1/me/monetization/xapps:
    get:
      summary: Get the signed-in user monetization overview across xapps
      tags: [Portal]
      security:
        - BearerAuth: []
      description: |
        Returns the current-user monetization overview grouped by xapp for the active or selected
        subject in the current portal session.

        By default only current/active monetized xapps are returned. Set `current_only=false`
        to include past monetization records as well.
      parameters:
        - name: subjectId
          in: query
          required: false
          schema: { type: string }
        - name: currentOnly
          in: query
          required: false
          schema: { type: boolean, default: true }
      responses:
        "200":
          description: Signed-in user monetization overview grouped across xapps for the selected subject.
          content:
            application/json:
              schema: { type: object }
        "400":
          description: Portal session has no active or linked subject for monetization reads
        "401":
          description: Unauthorized

  /v1/me/xapps/{xappId}/monetization/purchase-intents/prepare:
    post:
      summary: Prepare a signed-in user purchase intent for a published xapp package
      tags: [Portal]
      security:
        - BearerAuth: []
      description: |
        Prepares a current-user XMS purchase intent from the published xapp monetization catalog.

        The current-user portal lane remains single-scope on the monetization side, while
        optional `installationId` remains validated app context. `locale` and `country`
        may be supplied to evaluate offering/paywall targeting and price country rules.
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object }
      responses:
        "200":
          description: Prepared purchase intent for the selected package under the signed-in portal user scope.
          content:
            application/json:
              schema: { type: object }

  /v1/me/xapps/{xappId}/monetization/purchase-intents/{intentId}/payment-session:
    post:
      summary: Create a hosted payment session for a signed-in user purchase intent
      tags: [Portal]
      security:
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: intentId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object }
      responses:
        "200":
          description: Hosted payment session plus payment page URL for the prepared purchase intent.
          content:
            application/json:
              schema: { type: object }

  /v1/me/xapps/{xappId}/monetization/purchase-intents/{intentId}/payment-session/finalize:
    post:
      summary: Finalize a signed-in user hosted payment session into XMS access state
      tags: [Portal]
      description: |
        Reconciles the current hosted payment session for a prepared signed-in user purchase intent
        and issues/refetches XMS access state on the same current-user lane used by the portal
        marketplace surfaces.
      security:
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: intentId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema: { type: object }
      responses:
        "200":
          description: Finalized hosted payment session plus refreshed XMS access artifacts for the signed-in user.
          content:
            application/json:
              schema: { type: object }
        "400":
          description: Purchase intent is not linked to a payment session
        "401":
          description: Authentication required
        "404":
          description: Purchase intent, xapp, published version, or payment session not found
        "409":
          description: Payment session not paid yet or access issuance is not yet active for the product family

  /v1/xapps/{xappId}/monetization/wallet-accounts:
    get:
      summary: List xapp wallet accounts for one monetization scope
      tags: [Catalog]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: subject_id
          in: query
          required: false
          schema: { type: string }
        - name: installation_id
          in: query
          required: false
          schema: { type: string }
        - name: realm_ref
          in: query
          required: false
          schema: { type: string }
      responses:
        "200":
          description: Wallet accounts for the selected scope
          content:
            application/json:
              schema:
                type: object
                properties:
                  xapp_id: { type: string }
                  version_id: { type: string }
                  items:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        status: { type: string }
                        product_id: { type: [string, "null"] }
                        product_slug: { type: [string, "null"] }
                        currency: { type: string }
                        virtual_currency:
                          type: [object, "null"]
                          properties:
                            code: { type: string }
                            name: { type: [string, "null"] }
                            status: { type: string }
                            issuer_scope: { type: string }
                            issuer_ref: { type: [string, "null"] }
                            source_kind: { type: string }
                            product_family: { type: [string, "null"] }
                        source_kind: { type: string }
                        source_ref: { type: [string, "null"] }
                        state_version: { type: string }
                        balance_remaining: { type: [string, "null"] }

  /v1/xapps/{xappId}/monetization/wallet-accounts/{walletAccountId}/consume:
    post:
      summary: Consume credits from an xapp wallet account
      tags: [Catalog]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: walletAccountId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [amount]
              properties:
                amount: { type: string }
                source_ref: { type: [string, "null"] }
                metadata:
                  type: [object, "null"]
                  additionalProperties: true
      responses:
        "200":
          description: Updated wallet balance and access snapshot after credit consumption
          content:
            application/json:
              schema:
                type: object
                properties:
                  xapp_id: { type: string }
                  version_id: { type: string }
                  wallet_account: { type: object }
                  wallet_ledger: { type: object }
                  access_projection: { type: object }
                  snapshot_id: { type: string }

  /v1/xapps/{xappId}/monetization/wallet-ledger:
    get:
      summary: List xapp wallet ledger entries for one monetization scope
      tags: [Catalog]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: subject_id
          in: query
          required: false
          schema: { type: string }
        - name: installation_id
          in: query
          required: false
          schema: { type: string }
        - name: realm_ref
          in: query
          required: false
          schema: { type: string }
        - name: wallet_account_id
          in: query
          required: false
          schema: { type: string }
        - name: payment_session_id
          in: query
          required: false
          schema: { type: string }
        - name: request_id
          in: query
          required: false
          schema: { type: string }
        - name: settlement_ref
          in: query
          required: false
          schema: { type: string }
      responses:
        "200":
          description: Wallet ledger entries for the selected scope
          content:
            application/json:
              schema:
                type: object
                properties:
                  xapp_id: { type: string }
                  version_id: { type: string }
                  items:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        wallet_account_id: { type: string }
                        wallet_product_slug: { type: [string, "null"] }
                        event_kind: { type: string }
                        amount: { type: string }
                        currency: { type: string }
                        virtual_currency:
                          type: [object, "null"]
                          properties:
                            code: { type: string }
                            name: { type: [string, "null"] }
                            status: { type: string }
                            issuer_scope: { type: string }
                            issuer_ref: { type: [string, "null"] }
                            source_kind: { type: string }
                            product_family: { type: [string, "null"] }
                        reservation_ref: { type: [string, "null"] }
                        idempotency_key: { type: [string, "null"] }
                        purchase_intent_id: { type: [string, "null"] }
                        purchase_transaction_id: { type: [string, "null"] }
                        payment_session_id: { type: [string, "null"] }
                        request_id: { type: [string, "null"] }
                        settlement_ref: { type: [string, "null"] }
                        source_kind: { type: string }
                        source_ref: { type: [string, "null"] }
                        occurred_at: { type: [string, "null"] }

  /v1/xapps/{xappId}/monetization/purchase-intents/{intentId}/payment-session/finalize:
    post:
      summary: Finalize an xapp hosted purchase payment session
      tags: [Catalog]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: intentId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Reconciled payment plus issued XMS access state for the hosted purchase intent
          content:
            application/json:
              schema:
                type: object
                properties:
                  xapp_id: { type: string }
                  version_id: { type: string }
                  payment_session:
                    type: object
                    properties:
                      payment_session_id: { type: string }
                      status: { type: string }
                      amount: { type: string }
                      currency: { type: string }
                      issuer: { type: string }
                      return_url: { type: string }
                      cancel_url: { type: [string, "null"] }
                      xapps_resume: { type: [string, "null"] }
                  prepared_intent:
                    type: object
                    properties:
                      purchase_intent_id: { type: string }
                      status: { type: string }
                  transaction:
                    type: object
                    properties:
                      id: { type: string }
                      status: { type: string }
                      payment_session_id: { type: [string, "null"] }
                      request_id: { type: [string, "null"] }
                      amount: { type: string }
                      currency: { type: string }
                  issuance_mode: { type: [string, "null"] }
                  entitlement:
                    type: [object, "null"]
                    additionalProperties: true
                  subscription_contract:
                    type: [object, "null"]
                    additionalProperties: true
                  wallet_account:
                    type: [object, "null"]
                    additionalProperties: true
                  wallet_ledger:
                    type: [object, "null"]
                    additionalProperties: true
                  access_projection:
                    type: object
                    additionalProperties: true
                  snapshot_id: { type: [string, "null"] }

  /v1/installations:
    get:
      summary: List installations
      tags: [Installations]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Installations list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/Installation" }

    post:
      summary: Create an installation
      tags: [Installations]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [xappId]
              additionalProperties: false
              properties:
                xappId: { type: string }
                status: { type: string }
                versionStrategy: { type: string }
                pinnedVersionId: { type: string }
                config: { type: object }
                termsAccepted: { type: boolean }
      responses:
        "201":
          description: Installation created

  /v1/installations/{id}:
    patch:
      summary: Update an installation
      tags: [Installations]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                status: { type: string }
                versionStrategy: { type: string }
                pinnedVersionId: { type: string }
                config: { type: object }
      responses:
        "200":
          description: Installation updated

    delete:
      summary: Uninstall (soft-delete) an installation
      tags: [Installations]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Installation uninstalled
          content:
            application/json:
              schema:
                type: object
                properties:
                  installation: { $ref: "#/components/schemas/Installation" }
        "404":
          description: Installation not found

  /v1/installations/{id}/update:
    post:
      summary: Update installation to latest version (accept terms)
      tags: [Installations]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      description: |
        Atomically moves the installation to the latest published version.
        If the new version has terms, `termsAccepted: true` must be provided.
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                termsAccepted: { type: boolean }
      responses:
        "200":
          description: Installation updated to latest version
          content:
            application/json:
              schema:
                type: object
                properties:
                  installation: { $ref: "#/components/schemas/Installation" }
        "400":
          description: Terms not accepted
        "404":
          description: Installation or version not found

  /v1/installations/post-publish-refresh:
    post:
      summary: Refresh tenant post-publish installations
      tags: [Installations]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      description: |
        Ensures the default guard installation set exists for the authenticated tenant
        and refreshes installed publisher xapps to their latest published versions.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                guardSlugs:
                  type: array
                  items: { type: string }
                refreshSlugs:
                  type: array
                  items: { type: string }
      responses:
        "200":
          description: Post-publish refresh summary
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true

  /v1/installations/secrets:
    get:
      summary: List client-scoped platform secrets
      tags: [Installations]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: tag
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string }
      responses:
        "200":
          description: OK
    post:
      summary: Create client-scoped platform secret
      tags: [Installations]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
                tags:
                  type: array
                  items: { type: string }
                secret: { type: string }
                secretRef: { type: string }
                description: { type: string }
                expiresAt: { type: string, format: date-time }
      responses:
        "201":
          description: Created

  /v1/installations/secrets/{id}:
    get:
      summary: Get client-scoped platform secret
      tags: [Installations]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: OK
    patch:
      summary: Update client-scoped platform secret
      tags: [Installations]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
                tags:
                  type: array
                  items: { type: string }
                secret: { type: string }
                secretRef: { type: string }
                description: { type: string }
                status: { type: string }
                expiresAt: { type: string, format: date-time, nullable: true }
      responses:
        "200":
          description: Updated
    delete:
      summary: Delete client-scoped platform secret
      tags: [Installations]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: OK

  /v1/subjects/resolve:
    post:
      summary: Resolve or create a subject
      tags: [Subjects]
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [type, identifier]
              additionalProperties: false
              properties:
                type: { type: string }
                identifier:
                  type: object
                  required: [idType, value]
                  additionalProperties: false
                  properties:
                    idType: { type: string }
                    value: { type: string }
                    hint: { type: string }
                email: { type: string }
      responses:
        "200":
          description: Existing subject
          content:
            application/json:
              schema:
                type: object
                properties:
                  subjectId: { type: string }
        "201":
          description: Created subject
          content:
            application/json:
              schema:
                type: object
                properties:
                  subjectId: { type: string }

  /v1/subjects/relationships:
    post:
      summary: Create a subject relationship
      tags: [Subjects]
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [subjectId, relatedSubjectId, relationshipType]
              additionalProperties: false
              properties:
                subjectId: { type: string }
                relatedSubjectId: { type: string }
                relationshipType: { type: string }
                effectiveFrom:
                  type: string
                  format: date-time
                effectiveTo:
                  type: string
                  format: date-time
                evidence: {}
      responses:
        "201":
          description: Relationship created

  /v1/subjects/{id}/relationships:
    get:
      summary: List relationships for a subject
      tags: [Subjects]
      security:
        - ApiKeyAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
        - name: direction
          in: query
          required: false
          schema:
            type: string
            enum: [incoming, outgoing, both]
        - name: status
          in: query
          required: false
          schema:
            type: string
            description: Use `all` to include revoked relationships.
      responses:
        "200":
          description: Relationships list

  /v1/subjects/relationships/{id}/revoke:
    post:
      summary: Revoke a subject relationship
      tags: [Subjects]
      security:
        - ApiKeyAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                reason: { type: string }
      responses:
        "200":
          description: Relationship revoked

  /v1/subjects/policy-rules:
    post:
      summary: Create a policy rule version
      tags: [Subjects]
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [ruleKey, name, rule]
              additionalProperties: false
              properties:
                ruleKey: { type: string }
                name: { type: string }
                scope: { type: object, additionalProperties: true }
                rule: { type: object, additionalProperties: true }
                activate: { type: boolean }
      responses:
        "201":
          description: Policy rule version created
    get:
      summary: List policy rule versions
      tags: [Subjects]
      security:
        - ApiKeyAuth: []
      parameters:
        - name: status
          in: query
          required: false
          schema:
            type: string
            description: Use `all` to include every status.
        - name: ruleKey
          in: query
          required: false
          schema: { type: string }
        - name: limit
          in: query
          required: false
          schema: { type: integer, minimum: 1, maximum: 200 }
      responses:
        "200":
          description: Policy rule versions

  /v1/subjects/policy-rules/{id}/versions:
    post:
      summary: Create a new version from an existing policy rule
      tags: [Subjects]
      security:
        - ApiKeyAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                name: { type: string }
                scope: { type: object, additionalProperties: true }
                rule: { type: object, additionalProperties: true }
                activate: { type: boolean }
      responses:
        "201":
          description: Policy rule version created

  /v1/subjects/policy-rules/{id}/activate:
    post:
      summary: Activate a policy rule version
      tags: [Subjects]
      security:
        - ApiKeyAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Policy rule activated

  /v1/catalog-sessions:
    post:
      summary: Create a catalog embed session
      tags: [Embed]
      security:
        - ApiKeyAuth: []
      description: |
        Creates a short-lived catalog token for embedding the Marketplace Catalog in an iframe.

        Authentication:
        - Requires `X-API-Key` (server-to-server).

        Security notes:
        - `origin` is validated against `client_allowed_origins` for the calling client.
        - The returned `token` is intended to be supplied via query string (e.g. `?token=...`).
          The gateway responds with `Cache-Control: no-store` and `Referrer-Policy: no-referrer`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [origin]
              additionalProperties: false
              properties:
                origin:
                  type: string
                  description: Calling site origin that will embed the catalog iframe.
                subjectId:
                  type: string
                  description: Optional subject scope for the embedded session (enables subject-scoped features like "My Requests").
                customerProfile:
                  type: object
                  additionalProperties: true
                  description: |
                    Optional canonical billing-profile payload bound to the resolved subject for this
                    catalog session. This is consumed as `customerProfile` throughout embed/guard
                    remediation and may carry either direct billing fields or a selection contract
                    such as `selected_profile_source` / `selected_profile_id`.
                xappId:
                  type: string
                  description: Optional xapp ID to open directly in the catalog.
                publishers:
                  type: array
                  items: { type: string }
                  description: Optional list of publisher IDs to filter the catalog.
                tags:
                  type: array
                  items: { type: string }
                  description: Optional list of tags to filter the catalog.
      responses:
        "201":
          description: Session created
          content:
            application/json:
              schema:
                type: object
                required: [token, embedUrl]
                properties:
                  token: { type: string }
                  embedUrl:
                    type: string
                    description: URL to embed as an iframe (includes the `token` query parameter).
        "401":
          description: X-API-Key required
        "403":
          description: Origin not allowed

  /v1/widget-sessions:
    post:
      summary: Issue a new widget session token
      tags: [Widget Sessions]
      x-compatibility-class: A
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      description: |
        Creates a short-lived widget token and returns an `embedUrl` suitable for iframe embedding.

        Authentication:
        - `X-API-Key` (server-to-server), OR
        - `Authorization: Bearer <userSession>` (portal user session)

        Notes:
        - This endpoint requires an `Origin` header and validates it against `client_allowed_origins`.
        - For `type: "read"` widgets, if `requestId` is omitted the gateway may bind the widget session
          to the latest existing request for the widget’s bound tool (scoped by installation + subject when present).
          If no prior request exists, the hosted widget UI shows an empty state (no auto-run).
      parameters:
        - name: Origin
          in: header
          required: true
          schema: { type: string }
          description: Calling site origin. Must be allowed for the client (see `client_allowed_origins`).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              anyOf:
                - required: [installationId, widgetId]
                - required: [xappId, guardUi]
                - required: [xappId, guard_ui]
                - required: [xapp_id, guardUi]
                - required: [xapp_id, guard_ui]
              additionalProperties: false
              properties:
                installationId: { type: string }
                widgetId: { type: string }
                xappId: { type: string }
                xapp_id: { type: string }
                subjectId: { type: string }
                requestId: { type: string }
                locale: { type: string }
                hostReturnUrl: { type: string }
                host_return_url: { type: string }
                resultPresentation:
                  type: string
                  enum: [runtime_default, inline, publisher_managed]
                result_presentation:
                  type: string
                  enum: [runtime_default, inline, publisher_managed]
                guardUi:
                  $ref: "#/components/schemas/SubjectProfileGuardUiDescriptor"
                guard_ui:
                  $ref: "#/components/schemas/SubjectProfileGuardUiDescriptor"
      responses:
        "200":
          description: Session created
          content:
            application/json:
              schema:
                type: object
                required: [token, embedUrl]
                properties:
                  token: { type: string }
                  embedUrl: { type: string }
                  context:
                    type: object
                    nullable: true
                    description: Session context resolved for the widget runtime.
                    properties:
                      clientId: { type: string }
                      installationId: { type: string }
                      xappId: { type: string }
                      subjectId: { type: string, nullable: true }
                      requestId: { type: string, nullable: true }
                      resultPresentation:
                        type: string
                        enum: [runtime_default, inline, publisher_managed]
                      operational_surfaces:
                        $ref: "#/components/schemas/OperationalSurfacesDescriptor"
                  widget:
                    type: object
                    nullable: true
                    description: The specific widget metadata.
                  tool:
                    type: object
                    nullable: true
                    description: The tool metadata associated with the widget.
                  xappWidgets:
                    type: array
                    nullable: true
                    items:
                      type: object
                    description: Other widgets available for this xapp installation.
        "401":
          description: API key or user session required
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "403":
          description: Guard blocked widget session creation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GuardBlockedErrorResponse"
              examples:
                beforeSessionOpenSubscriptionBlocked:
                  summary: "before:session_open subscription guard denied widget session"
                  value:
                    message: Subscription tier is insufficient for this tool
                    code: GUARD_BLOCKED
                    guardSlug: subscription-check
                    guard_slug: subscription-check
                    trigger: "before:session_open"
                    reason: insufficient_tier
                    details:
                      policy_kind: subscription
                      uiRequired: true
                      requiredTier: pro
                      currentTier: basic
                      monetization_state:
                        entitlement_state: active
                        balance_state: unknown
                        payment_attempt_state: unknown
                        tier: basic
                        issuer: tenant
                    action:
                      kind: upgrade_subscription
                    guard:
                      slug: subscription-check
                      trigger: "before:session_open"
                      reason: insufficient_tier
                      message: Subscription tier is insufficient for this tool
                beforeSessionOpenPaymentBlocked:
                  summary: "before:session_open payment guard blocked widget session"
                  value:
                    message: Payment is required before opening this session
                    code: GUARD_BLOCKED
                    guardSlug: payment-gate
                    guard_slug: payment-gate
                    trigger: "before:session_open"
                    reason: payment_required
                    details:
                      policy_kind: payment
                      uiRequired: true
                      mode: one_time
                      pricing:
                        currency: USD
                        default_amount: 3
                        description: Weather lookup fee
                        asset: null
                        pay_to: null
                      accepts:
                        - scheme: mock_manual
                          contract: xapps_payment_orchestration_v1
                          currency: USD
                          amount: "3.00"
                      resource:
                        xapp_id: 01HXYZPAYMENTXAPP
                      paymentStatus: unknown
                      monetization_state:
                        entitlement_state: unknown
                        balance_state: unknown
                        payment_attempt_state: unknown
                    action:
                      kind: complete_payment
                    guard:
                      slug: payment-gate
                      trigger: "before:session_open"
                      reason: payment_required
                      message: Payment is required before opening this session
                beforeSessionOpenPaymentBlockedDelegated:
                  summary: "before:session_open delegated payment guard blocked widget session"
                  value:
                    message: Payment is required before opening this session
                    code: GUARD_BLOCKED
                    guardSlug: payment-gate
                    guard_slug: payment-gate
                    trigger: "before:session_open"
                    reason: payment_required
                    details:
                      policy_kind: payment
                      uiRequired: true
                      mode: one_time
                      pricing:
                        currency: USD
                        default_amount: 3
                        description: Weather lookup fee
                        asset: null
                        pay_to: null
                      accepts:
                        - scheme: stripe_checkout
                          contract: xapps_payment_orchestration_v1
                          currency: USD
                          amount: "3.00"
                      resource:
                        xapp_id: 01HXYZPAYMENTXAPP
                      paymentStatus: unknown
                      monetization_state:
                        entitlement_state: unknown
                        balance_state: unknown
                        payment_attempt_state: unknown
                        issuer: tenant_delegated
                        authority_lane: tenant_delegated
                        source_ref: tenant-policy/delegated-default
                    action:
                      kind: complete_payment
                    guard:
                      slug: payment-gate
                      trigger: "before:session_open"
                      reason: payment_required
                      message: Payment is required before opening this session
                beforeSessionOpenInsufficientCreditsBlocked:
                  summary: "before:session_open credits guard blocked widget session"
                  value:
                    message: Insufficient credits for request execution
                    code: GUARD_BLOCKED
                    guardSlug: credit-balance-check
                    guard_slug: credit-balance-check
                    trigger: "before:session_open"
                    reason: insufficient_credits
                    details:
                      policy_kind: credits
                      uiRequired: true
                      required_credits: 3
                      available_credits: 1
                      currency: credits
                      monetization_state:
                        entitlement_state: active
                        balance_state: insufficient
                        payment_attempt_state: unknown
                        tier: pro
                        issuer: tenant
                    action:
                      kind: open_guard
                    guard:
                      slug: credit-balance-check
                      trigger: "before:session_open"
                      reason: insufficient_credits
                      message: Insufficient credits for request execution

  /v1/uploads:
    post:
      summary: Create an upload session (legacy direct upload OR multipart session create)
      tags: [Uploads]
      security:
        - BearerAuth: []
      description: |
        Dual-mode endpoint:
        - **Legacy**: upload bytes directly via `multipart/form-data` or `application/json` with `{ base64 }` (stored in `temp_uploads`).
        - **Multipart**: create an upload session via `application/json` with `{ purpose, owner, filename, ... }`, then upload parts.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                purpose: { type: string, example: request_artifact }
                owner:
                  type: object
                  properties:
                    type: { type: string, example: widget_session }
                    id: { type: string }
                filename: { type: string }
                mimeType: { type: string }
                sizeBytes: { type: number }
                sha256: { type: string }
                multipart:
                  type: object
                  properties:
                    preferredPartSizeBytes: { type: number, example: 8388608 }
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    required: [uploadId, status, expiresAt, multipart]
                    properties:
                      uploadId: { type: string }
                      status: { type: string, example: initiated }
                      expiresAt: { type: string }
                      expectedSizeBytes: { type: number, nullable: true }
                      uploadedBytes: { type: number }
                      partsUploaded: { type: number }
                      progressPct: { type: number, nullable: true, example: 0 }
                      multipart:
                        type: object
                        required: [partSizeBytes, minPartNumber, maxPartNumber, minPartSizeBytes]
                        properties:
                          partSizeBytes: { type: number }
                          minPartNumber: { type: integer }
                          maxPartNumber: { type: integer }
                          minPartSizeBytes: { type: number }
                  - type: object
                    required: [uploadId, status, artifactRef, expiresAt]
                    properties:
                      uploadId: { type: string }
                      status: { type: string, example: completed }
                      artifactRef:
                        type: object
                        required: [uploadId, filename, mimeType, sizeBytes, checksumSha256]
                        properties:
                          uploadId: { type: string }
                          filename: { type: string }
                          mimeType: { type: string }
                          sizeBytes: { type: number }
                          checksumSha256: { type: string }
                      expiresAt: { type: string }
                      expectedSizeBytes: { type: number }
                      uploadedBytes: { type: number }
                      partsUploaded: { type: number }
                      progressPct: { type: number, nullable: true, example: 100 }

  /v1/uploads/{uploadId}/parts/{partNumber}:
    put:
      summary: Upload a multipart part
      tags: [Uploads]
      security:
        - BearerAuth: []
      parameters:
        - name: uploadId
          in: path
          required: true
          schema: { type: string }
        - name: partNumber
          in: path
          required: true
          schema: { type: integer, minimum: 1 }
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "200":
          description: Uploaded
          content:
            application/json:
              schema:
                type: object
                required: [etag, sizeBytes]
                properties:
                  etag: { type: string }
                  sizeBytes: { type: number }
                  status: { type: string, example: initiated }
                  expectedSizeBytes: { type: number, nullable: true }
                  uploadedBytes: { type: number }
                  partsUploaded: { type: number }
                  progressPct: { type: number, nullable: true, example: 64 }

  /v1/uploads/{uploadId}:
    get:
      summary: Get upload status and progress
      tags: [Uploads]
      security:
        - BearerAuth: []
      parameters:
        - name: uploadId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Upload details
          content:
            application/json:
              schema:
                type: object
                required: [uploadId, status, filename, mimeType, uploadedBytes, partsUploaded]
                properties:
                  uploadId: { type: string }
                  status: { type: string, example: initiated }
                  purpose: { type: string }
                  owner:
                    type: object
                    properties:
                      type: { type: string }
                      id: { type: string }
                  filename: { type: string }
                  mimeType: { type: string }
                  expectedSizeBytes: { type: number, nullable: true }
                  expectedSha256: { type: string, nullable: true }
                  sizeBytes: { type: number, nullable: true }
                  sha256: { type: string, nullable: true }
                  storage:
                    type: object
                    nullable: true
                    properties:
                      provider: { type: string }
                      key: { type: string }
                  expiresAt: { type: string, nullable: true }
                  completedAt: { type: string, nullable: true }
                  attachedAt: { type: string, nullable: true }
                  attachedRequestId: { type: string, nullable: true }
                  uploadedBytes: { type: number }
                  partsUploaded: { type: number }
                  progressPct: { type: number, nullable: true }
        "404":
          description: Upload not found

  /v1/uploads/{uploadId}/parts:
    get:
      summary: List uploaded multipart parts
      tags: [Uploads]
      security:
        - BearerAuth: []
      parameters:
        - name: uploadId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Parts list with progress
          content:
            application/json:
              schema:
                type: object
                required: [items]
                properties:
                  uploadId: { type: string }
                  status: { type: string }
                  expiresAt: { type: string, nullable: true }
                  expectedSizeBytes: { type: number, nullable: true }
                  uploadedBytes: { type: number }
                  partsUploaded: { type: number }
                  progressPct: { type: number, nullable: true }
                  items:
                    type: array
                    items:
                      type: object
                      required: [partNumber, etag, sizeBytes]
                      properties:
                        partNumber: { type: integer }
                        etag: { type: string }
                        sizeBytes: { type: number }

  /v1/uploads/{uploadId}/complete:
    post:
      summary: Complete a multipart upload
      tags: [Uploads]
      security:
        - BearerAuth: []
      parameters:
        - name: uploadId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [parts]
              additionalProperties: false
              properties:
                parts:
                  type: array
                  items:
                    type: object
                    required: [partNumber, etag]
                    additionalProperties: false
                    properties:
                      partNumber: { type: integer }
                      etag: { type: string }
      responses:
        "200":
          description: Completed
          content:
            application/json:
              schema:
                type: object
                required: [uploadId, status, sizeBytes, sha256]
                properties:
                  uploadId: { type: string }
                  status: { type: string, example: completed }
                  expectedSizeBytes: { type: number, nullable: true }
                  uploadedBytes: { type: number, nullable: true }
                  partsUploaded: { type: number, nullable: true }
                  progressPct: { type: number, nullable: true, example: 100 }
                  sizeBytes: { type: number }
                  sha256: { type: string }
                  storage:
                    type: object
                    properties:
                      provider: { type: string }
                      key: { type: string }

  /v1/signing/nonces:
    post:
      summary: Mint a fresh signing nonce
      tags: [Security]
      description: |
        Returns a fresh, short-lived nonce for subject proof.
        Nonces are single-use and bound to (installationId, subjectId).
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [installationId, subjectId]
              additionalProperties: false
              properties:
                installationId: { type: string }
                subjectId: { type: string }
      responses:
        "200":
          description: Nonce returned
          content:
            application/json:
              schema:
                type: object
                required: [nonce, ttlSeconds, issuedAt]
                additionalProperties: false
                properties:
                  nonce:
                    type: string
                    description: base64url (no padding)
                  ttlSeconds:
                    type: integer
                  issuedAt:
                    type: string
                    description: RFC3339 UTC

  /v1/signing/subject-keys/active:
    get:
      summary: Resolve the active subject key for signing (widget scope)
      tags: [Security]
      description: |
        Returns the currently active subject key for the calling widget token's `(installationId, subjectId)`.
        This keeps browser clients simple: they don't need to query the database to find the key id.

        For WebAuthn keys, `credentialId` is returned so the browser can bind `allowCredentials`.
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Active subject key
          content:
            application/json:
              schema:
                type: object
                required: [keyId, credentialType, algorithm]
                additionalProperties: false
                properties:
                  keyId: { type: string, description: subjectKeyId (`kid`) }
                  credentialType: { type: string, enum: [asymmetric, webauthn] }
                  algorithm: { type: string }
                  credentialId:
                    type: string
                    nullable: true
                    description: WebAuthn credential id (base64url) when `credentialType=webauthn`
                  label: { type: string, nullable: true }
                  createdAt: { type: string, nullable: true }
        "404":
          description: No active subject key for this subject/installation

  /v1/subject/keys:
    get:
      summary: List subject keys
      tags: [Security]
      security:
        - BearerAuth: []
      responses:
        "200":
          description: List of subject keys
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/SubjectKey" }
    post:
      summary: Register a new subject key
      tags: [Security]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [label]
              properties:
                publicKey:
                  type: string
                  description: "Ed25519 public key (base64url, no padding). For passkeys, use `/v1/subject/webauthn/register/*`."
                label: { type: string }
                attestation:
                  {
                    type: object,
                    description: "Deprecated. Use `/v1/subject/webauthn/register/*` for passkeys.",
                  }
      responses:
        "201":
          description: Key registration initiated
          content:
            application/json:
              schema:
                type: object
                properties:
                  id: { type: string }
                  challenge:
                    {
                      type: string,
                      description: "Challenge to be signed by the private key to activate",
                    }

  /v1/subject/keys/{id}/verify:
    post:
      summary: Verify and activate a subject key
      tags: [Security]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [signature, challenge]
              properties:
                signature:
                  type: string
                  description: "Signature of the challenge (base64url, no padding)"
                challenge:
                  type: string
                  description: "The base64url challenge returned by `/v1/subject/keys`"
      responses:
        "200":
          description: Subject key verified and activated
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string }
        "400":
          description: Invalid signature or challenge

  /v1/subject/webauthn/register/options:
    post:
      summary: Begin WebAuthn/passkey registration
      tags: [Security]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [label]
              additionalProperties: false
              properties:
                label:
                  type: string
                  description: Human label for the passkey (e.g. "MacBook TouchID")
      responses:
        "200":
          description: WebAuthn registration options
          content:
            application/json:
              schema:
                type: object
                required: [options]
                properties:
                  options:
                    type: object
                    description: PublicKeyCredentialCreationOptionsJSON (binary fields base64url)

  /v1/subject/webauthn/register/verify:
    post:
      summary: Verify WebAuthn/passkey registration and create an active subject key
      tags: [Security]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [attestation]
              additionalProperties: false
              properties:
                attestation:
                  type: object
                  description: RegistrationResponseJSON (binary fields base64url)
      responses:
        "201":
          description: WebAuthn subject key created
          content:
            application/json:
              schema:
                type: object
                required: [id, keyId]
                properties:
                  id: { type: string }
                  keyId: { type: string }

  /v1/subject/keys/{id}:
    delete:
      summary: Revoke a subject key
      tags: [Security]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "204":
          description: Key revoked

  /v1/subject/installations/{installationId}/keys:
    post:
      summary: Register a subject key (widget scope)
      tags: [Security]
      security:
        - BearerAuth: []
      description: |
        Registers a new Ed25519 public key for a subject within a specific installation scope.
        Auth: `Authorization: Bearer <widgetToken>`.
      parameters:
        - name: installationId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [algorithm, publicKey]
              additionalProperties: false
              properties:
                algorithm: { type: string, enum: [ed25519] }
                publicKey: { type: string }
                expiresAt: { type: string, format: date-time }
      responses:
        "201":
          description: Key registered (pending verification)
        "409":
          description: Public key already bound to another installation

  /v1/subject/installations/{installationId}/keys/{keyId}:
    delete:
      summary: Revoke a subject key (widget scope)
      tags: [Security]
      security:
        - BearerAuth: []
      parameters:
        - name: installationId
          in: path
          required: true
          schema: { type: string }
        - name: keyId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Key revoked
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string }

  /v1/subject/installations/{installationId}/keys/{keyId}/challenge:
    post:
      summary: Get verification challenge for a subject key (widget scope)
      tags: [Security]
      security:
        - BearerAuth: []
      parameters:
        - name: installationId
          in: path
          required: true
          schema: { type: string }
        - name: keyId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Challenge issued
          content:
            application/json:
              schema:
                type: object
                properties:
                  challenge: { type: string }

  /v1/subject/installations/{installationId}/keys/{keyId}/verify:
    post:
      summary: Verify and activate a subject key (widget scope)
      tags: [Security]
      security:
        - BearerAuth: []
      parameters:
        - name: installationId
          in: path
          required: true
          schema: { type: string }
        - name: keyId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [signature, nonce]
              additionalProperties: false
              properties:
                signature: { type: string, description: "base64url signature" }
                nonce: { type: string }
      responses:
        "200":
          description: Key verified and activated
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string }
                  verified_by_subject: { type: boolean }
        "400":
          description: Invalid signature or challenge

  /v1/subject/installations/{installationId}/signing-nonce:
    post:
      summary: Get a signing nonce (widget scope)
      tags: [Security]
      security:
        - BearerAuth: []
      description: |
        Returns a single-use nonce for subject signing, scoped to the installation.
        Auth: `Authorization: Bearer <widgetToken>`.
      parameters:
        - name: installationId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Nonce issued
          content:
            application/json:
              schema:
                type: object
                properties:
                  nonce: { type: string }
                  ttlSeconds: { type: integer }

  /v1/requests:
    get:
      summary: List requests in a scope
      tags: [Requests]
      description: |
        Lists requests for a given scope with pagination.

        **Widget-token callers**: scope is derived from the token (installation + subject + widget-bound tool).
        **API-key callers**: must provide `installationId` and `toolName` query params.
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: installationId
          in: query
          schema: { type: string }
          description: Required for API-key callers.
        - name: toolName
          in: query
          schema: { type: string }
          description: Required for API-key callers.
        - name: subjectId
          in: query
          schema: { type: string }
        - name: page
          in: query
          schema: { type: integer, minimum: 1 }
        - name: pageSize
          in: query
          schema: { type: integer, minimum: 1, maximum: 100 }
      responses:
        "200":
          description: Requests list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/Request" }
                  pagination:
                    type: object
                    properties:
                      total: { type: integer }
                      page: { type: integer }
                      pageSize: { type: integer }
                      totalPages: { type: integer }

    post:
      summary: Submit a new request
      tags: [Requests]
      x-compatibility-class: A
      description: |
        Submit/execute a tool request.

        Auth modes:
        - Client backend: `X-API-Key`.
        - Browser widgets: `Authorization: Bearer <widgetToken>` + `Origin` (preferred; `Referer` may be used as fallback).
        - Publisher server-to-server (widget surface): `Authorization: Bearer <publisher_widget_token>`.

        Capability note (widget-like tokens):
        - Widgets may be capability-gated by `requests:execute`.
        - For backwards compatibility, `type: "write"` widgets may execute even without an explicit capability.

        **Subject Proof (high-assurance tools)**:
        If the tool's `signature_policy` requires proof, the request body MUST include:
        - `subjectActionPayload`: base64url(UTF-8(JCS(subjectAction)))
        - `subjectProof`: either `{ type: "jws", jws: "..." }` (Ed25519 JWS) or `{ type: "webauthn", kid: "...", webauthn: { ... } }` (WebAuthn assertion)

        All binary values are base64url (no padding).
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema: { type: string }
          description: Optional idempotency key to safely retry request creation.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [toolName, payload]
              additionalProperties: false
              properties:
                installationId: { type: string }
                toolName: { type: string }
                subjectId: { type: string }
                locale: { type: string }
                payload: { type: object }
                clientRequestRef: { type: string }
                externalRequestId: { type: string }
                subjectActionPayload:
                  type: string
                  description: base64url(UTF-8(JCS(subjectAction)))
                subjectProof:
                  type: object
                  description: Subject proof (JWS EdDSA or WebAuthn assertion)
                  additionalProperties: false
                  required: [type]
                  properties:
                    type:
                      type: string
                      enum: [jws, webauthn]
                    kid:
                      type: string
                      description: subjectKeyId (required for webauthn; for jws it is in the JWS header)
                    jws:
                      type: string
                      description: JWS compact serialization (alg=EdDSA; header.kid=subjectKeyId)
                    webauthn:
                      type: object
                      description: WebAuthn AuthenticationResponseJSON (all binary fields base64url)
                      additionalProperties: true
                # Legacy subjectSignature* fields are deprecated; prefer subjectActionPayload + subjectProof.
                uploads:
                  type: array
                  description: Completed uploads to attach as input artifacts (upload-first UX)
                  items:
                    type: object
                    required: [uploadId]
                    additionalProperties: false
                    properties:
                      uploadId: { type: string }
                      fieldPath: { type: string }
                uploadIds:
                  type: array
                  description: Convenience form of `uploads` when no field paths are needed
                  items: { type: string }
                guardOrchestration:
                  type: object
                  description: |
                    Optional host-approved guard orchestration retry payload.
                    Keyed by guard slug. Used for non-headless `before:tool_run` flows
                    after host/user confirmation (for example tenant-owned `pay_per_request` guards).
                  additionalProperties:
                    $ref: "#/components/schemas/GuardOrchestrationApproval"
      responses:
        "200":
          description: Existing request returned for idempotency key
          content:
            application/json:
              schema:
                type: object
                properties:
                  request: { $ref: "#/components/schemas/Request" }
        "201":
          description: Request created
          content:
            application/json:
              schema:
                type: object
                properties:
                  request: { $ref: "#/components/schemas/Request" }
        "403":
          description: Guard blocked request execution
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GuardBlockedErrorResponse"
              examples:
                beforeToolRunBlocked:
                  summary: "before:tool_run guard denied by policy"
                  value:
                    message: Subscription tier required
                    code: GUARD_BLOCKED
                    guardSlug: subscription-check
                    guard_slug: subscription-check
                    trigger: "before:tool_run"
                    reason: subscription_missing
                    details:
                      required_tier: pro
                      current_tier: free
                      monetization_state:
                        entitlement_state: inactive
                        balance_state: unknown
                        payment_attempt_state: unknown
                        tier: free
                    action:
                      kind: upgrade_subscription
                      trigger: "before:tool_run"
                    guard:
                      slug: subscription-check
                      trigger: "before:tool_run"
                      reason: subscription_missing
                      message: Subscription tier required
                      details:
                        required_tier: pro
                        current_tier: free
                beforeToolRunInsufficientCreditsBlocked:
                  summary: "prepaid credit balance is insufficient for request execution"
                  value:
                    message: Insufficient credits for request execution
                    code: GUARD_BLOCKED
                    guardSlug: credit-balance-check
                    guard_slug: credit-balance-check
                    trigger: "before:tool_run"
                    reason: insufficient_credits
                    details:
                      required_credits: 3
                      available_credits: 1
                      currency: credits
                      monetization_state:
                        entitlement_state: active
                        balance_state: insufficient
                        payment_attempt_state: unknown
                        tier: pro
                    action:
                      kind: open_guard
                      guardSlug: credit-balance-check
                      trigger: "before:tool_run"
                    guard:
                      slug: credit-balance-check
                      trigger: "before:tool_run"
                      reason: insufficient_credits
                      message: Insufficient credits for request execution
                      details:
                        required_credits: 3
                        available_credits: 1
                        currency: credits
                beforeToolRunPaymentOrchestrationRequired:
                  summary: "tenant pay-per-request guard requires UI orchestration"
                  value:
                    message: Payment required before request execution
                    code: GUARD_BLOCKED
                    guardSlug: tenant-payment-gate
                    guard_slug: tenant-payment-gate
                    trigger: "before:tool_run"
                    reason: ui_orchestration_required
                    details:
                      pricing_model: pay_per_request
                      tenant_slug: tenant-a
                      xapp_slug: extensions-lab-guard-ui-orchestration
                      amount: 0.05
                      currency: USD
                    action:
                      kind: open_guard
                      guardSlug: tenant-payment-gate
                      trigger: "before:tool_run"
                    guard:
                      slug: tenant-payment-gate
                      trigger: "before:tool_run"
                      reason: ui_orchestration_required
                      message: Payment required before request execution
                      details:
                        pricing_model: pay_per_request
                        tenant_slug: tenant-a
                        xapp_slug: extensions-lab-guard-ui-orchestration
                        amount: 0.05
                        currency: USD
                beforeToolRunPaymentReplayBlocked:
                  summary: "pay-per-request evidence replay is rejected deterministically"
                  value:
                    message: Payment evidence was already used for a previous request
                    code: GUARD_BLOCKED
                    guardSlug: extensions-lab-tenant-payment-policy
                    guard_slug: extensions-lab-tenant-payment-policy
                    trigger: "before:tool_run"
                    reason: payment_receipt_already_used
                    details:
                      payment_session_id: pay_1771619991679
                      receipt_id: rcpt_1771620005744_3471qka9
                      issuer: tenant
                      pricing:
                        currency: USD
                        default_amount: 3
                        description: null
                        asset: null
                        pay_to: null
                      resource:
                        xapp_id: 01HXYZPAYMENTXAPP
                        tool_name: lookup_weather_now
                      accepts:
                        - scheme: mock_manual
                          contract: xapps_payment_orchestration_v1
                          currency: USD
                          amount: "3.00"
                      monetization_state:
                        payment_attempt_state: replayed
                        entitlement_state: unknown
                        balance_state: unknown
                    action:
                      kind: complete_payment
                      guardSlug: extensions-lab-tenant-payment-policy
                      trigger: "before:tool_run"
                    guard:
                      slug: extensions-lab-tenant-payment-policy
                      trigger: "before:tool_run"
                      reason: payment_receipt_already_used
                      message: Payment evidence was already used for a previous request
                      details:
                        payment_session_id: pay_1771619991679
                        receipt_id: rcpt_1771620005744_3471qka9
                        issuer: tenant
                beforeToolRunPaymentNotSettled:
                  summary: "signed payment evidence exists but is not settled"
                  value:
                    message: Payment evidence indicates payment is not settled
                    code: GUARD_BLOCKED
                    guardSlug: tenant-payment-policy
                    guard_slug: tenant-payment-policy
                    trigger: "before:tool_run"
                    reason: payment_not_settled
                    details:
                      status: failed
                      pricing:
                        currency: USD
                        default_amount: 3
                        description: null
                        asset: null
                        pay_to: null
                      resource:
                        xapp_id: 01HXYZPAYMENTXAPP
                        tool_name: lookup_weather_now
                      accepts:
                        - scheme: mock_manual
                          contract: xapps_payment_orchestration_v1
                          currency: USD
                          amount: "3.00"
                      monetization_state:
                        payment_attempt_state: failed
                        entitlement_state: unknown
                        balance_state: unknown
                    action:
                      kind: complete_payment
                      guardSlug: tenant-payment-policy
                      trigger: "before:tool_run"
                    guard:
                      slug: tenant-payment-policy
                      trigger: "before:tool_run"
                      reason: payment_not_settled
                      message: Payment evidence indicates payment is not settled
                      details:
                        status: failed
                beforeToolRunPaymentNotSettledDelegated:
                  summary: "delegated signed payment evidence exists but is not settled"
                  value:
                    message: Payment evidence indicates payment is not settled
                    code: GUARD_BLOCKED
                    guardSlug: publisher-delegated-payment-policy
                    guard_slug: publisher-delegated-payment-policy
                    trigger: "before:tool_run"
                    reason: payment_not_settled
                    details:
                      status: failed
                      issuer: publisher_delegated
                      pricing:
                        currency: USD
                        default_amount: 3
                        description: null
                        asset: null
                        pay_to: null
                      resource:
                        xapp_id: 01HXYZPAYMENTXAPP
                        tool_name: lookup_weather_now
                      accepts:
                        - scheme: paypal
                          contract: xapps_payment_orchestration_v1
                          currency: USD
                          amount: "3.00"
                      monetization_state:
                        payment_attempt_state: failed
                        entitlement_state: unknown
                        balance_state: unknown
                        issuer: publisher_delegated
                        authority_lane: publisher_delegated
                        source_ref: publisher-policy/delegated-default
                    action:
                      kind: complete_payment
                      guardSlug: publisher-delegated-payment-policy
                      trigger: "before:tool_run"
                    guard:
                      slug: publisher-delegated-payment-policy
                      trigger: "before:tool_run"
                      reason: payment_not_settled
                      message: Payment evidence indicates payment is not settled
                      details:
                        status: failed
                beforeToolRunSharingPolicyDeny:
                  summary: "sharing-policy-core deny uses canonical policy reason code"
                  value:
                    message: Requested purpose is not allowed by sharing policy
                    code: GUARD_BLOCKED
                    guardSlug: sharing-policy-core
                    guard_slug: sharing-policy-core
                    trigger: "before:tool_run"
                    reason: PURPOSE_NOT_ALLOWED
                    details:
                      purpose: marketing
                      legalBasis: consent
                      requestedDataScope:
                        - profile.email
                        - profile.phone
                      policy_context_summary:
                        purpose: marketing
                        legal_basis: consent
                        consent_ref_present: false
                        requested_data_scope_count: 2
                        target_subject_present: true
                      monetization_state:
                        entitlement_state: active
                        balance_state: unknown
                        payment_attempt_state: unknown
                    guard:
                      slug: sharing-policy-core
                      trigger: "before:tool_run"
                      reason: PURPOSE_NOT_ALLOWED
                      message: Requested purpose is not allowed by sharing policy
                      details:
                        purpose: marketing
                        legalBasis: consent
                        requestedDataScope:
                          - profile.email
                          - profile.phone
                        policy_context_summary:
                          purpose: marketing
                          legal_basis: consent
                          consent_ref_present: false
                          requested_data_scope_count: 2
                          target_subject_present: true

  /v1/threads:
    post:
      summary: Create a conversation thread
      tags: [Requests]
      x-compatibility-class: A
      description: |
        Creates a thread scoped to the widget token installation/subject.

        Guard baseline:
        - Evaluates `before:thread_create` guard chain from the effective installed manifest.
        - On deny, returns canonical `GUARD_BLOCKED` envelope.
      security:
        - BearerAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
              properties:
                title: { type: string }
                metadata:
                  type: object
                  additionalProperties: true
                hostReturnUrl:
                  type: string
                  description: Host return URL for payment/orchestration retry context.
                host_return_url:
                  type: string
                  description: Snake-case alias of `hostReturnUrl`.
                guardOrchestration:
                  type: object
                  additionalProperties: true
      responses:
        "201":
          description: Thread created
          content:
            application/json:
              schema:
                type: object
                properties:
                  thread:
                    type: object
                    properties:
                      id: { type: string }
                      client_id: { type: string }
                      installation_id: { type: string }
                      xapp_id: { type: string }
                      subject_id: { type: string, nullable: true }
                      status: { type: string, example: open }
                      title: { type: string, nullable: true }
                      metadata_jsonb:
                        type: object
                        additionalProperties: true
                      created_at: { type: string, format: date-time }
                      updated_at: { type: string, format: date-time }
        "403":
          description: Guard blocked thread creation
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GuardBlockedErrorResponse"
              examples:
                beforeThreadCreateBlocked:
                  summary: "before:thread_create guard denied by policy"
                  value:
                    message: Threads are temporarily unavailable
                    code: GUARD_BLOCKED
                    guardSlug: maintenance-mode
                    guard_slug: maintenance-mode
                    trigger: "before:thread_create"
                    reason: maintenance_mode
                    guard:
                      slug: maintenance-mode
                      trigger: "before:thread_create"
                      reason: maintenance_mode
                beforeThreadCreateSubscriptionBlocked:
                  summary: "before:thread_create subscription guard denied thread creation"
                  value:
                    message: Subscription tier is insufficient for this tool
                    code: GUARD_BLOCKED
                    guardSlug: subscription-check
                    guard_slug: subscription-check
                    trigger: "before:thread_create"
                    reason: insufficient_tier
                    details:
                      policy_kind: subscription
                      uiRequired: true
                      requiredTier: pro
                      currentTier: basic
                      monetization_state:
                        entitlement_state: active
                        balance_state: unknown
                        payment_attempt_state: unknown
                        tier: basic
                        issuer: tenant
                    action:
                      kind: upgrade_subscription
                    guard:
                      slug: subscription-check
                      trigger: "before:thread_create"
                      reason: insufficient_tier
                beforeThreadCreateInsufficientCreditsBlocked:
                  summary: "before:thread_create credits guard denied thread creation"
                  value:
                    message: Insufficient credits for request execution
                    code: GUARD_BLOCKED
                    guardSlug: credit-balance-check
                    guard_slug: credit-balance-check
                    trigger: "before:thread_create"
                    reason: insufficient_credits
                    details:
                      policy_kind: credits
                      uiRequired: true
                      required_credits: 2
                      available_credits: 0
                      currency: credits
                      monetization_state:
                        entitlement_state: active
                        balance_state: insufficient
                        payment_attempt_state: unknown
                        tier: pro
                        issuer: tenant
                    action:
                      kind: open_guard
                    guard:
                      slug: credit-balance-check
                      trigger: "before:thread_create"
                      reason: insufficient_credits
                beforeThreadCreatePaymentBlockedDelegated:
                  summary: "before:thread_create delegated payment guard denied thread creation"
                  value:
                    message: Payment is required before creating this thread
                    code: GUARD_BLOCKED
                    guardSlug: payment-gate
                    guard_slug: payment-gate
                    trigger: "before:thread_create"
                    reason: payment_required
                    details:
                      policy_kind: payment
                      uiRequired: true
                      mode: one_time
                      pricing:
                        currency: USD
                        default_amount: 3
                        description: Conversation start fee
                        asset: null
                        pay_to: null
                      accepts:
                        - scheme: paypal
                          contract: xapps_payment_orchestration_v1
                          currency: USD
                          amount: "3.00"
                      resource:
                        xapp_id: 01HXYZPAYMENTXAPP
                      paymentStatus: unknown
                      monetization_state:
                        entitlement_state: unknown
                        balance_state: unknown
                        payment_attempt_state: unknown
                        issuer: publisher_delegated
                        authority_lane: publisher_delegated
                        source_ref: publisher-policy/delegated-default
                    action:
                      kind: complete_payment
                    guard:
                      slug: payment-gate
                      trigger: "before:thread_create"
                      reason: payment_required

  /v1/requests/latest:
    get:
      summary: Resolve latest request for a scope
      tags: [Requests]
      description: |
        Helper endpoint used by read-only widgets to discover the latest request for the
        current widget scope (Installation + Subject + Tool).

        **Origin binding (Security)**:
        For browser-based widget calls (`Authorization: Bearer <widgetToken>`), the gateway strictly
        validates the caller's origin against the `allowedOrigins` list encoded in the token.
        - The gateway prefers the `Origin` header.
        - Fallback: `Referer` header.
        - Last fallback (controlled environments): `Host` + `X-Forwarded-Proto` + `X-Forwarded-Host`.

        **Auth Nuances**:
        - **Widget-token callers**: scope is automatically derived from the token (installation + subject + tool).
        - **API-key callers**: you must explicitly provide `installationId` + `toolName` (and optionally `subjectId`) as query params.
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: installationId
          in: query
          required: false
          schema: { type: string }
          description: Required for API-key callers.
        - name: toolName
          in: query
          required: false
          schema: { type: string }
          description: Required for API-key callers.
        - name: subjectId
          in: query
          required: false
          schema: { type: string }
          description: Optional filter for API-key callers.
      responses:
        "200":
          description: Latest request resolved
          content:
            application/json:
              schema:
                type: object
                properties:
                  requestId:
                    type: string
                    nullable: true
                    description: Latest request id, or null if no matching request exists.

  /v1/requests/{id}:
    get:
      summary: Get request details
      tags: [Requests]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Request found
          content:
            application/json:
              schema:
                type: object
                properties:
                  request: { $ref: "#/components/schemas/Request" }

  /v1/requests/{id}/payment/reconcile:
    post:
      summary: Reconcile payment finality for a widget/request-scoped request
      tags: [Requests]
      x-compatibility-class: A
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RequestPaymentReconcileRequest"
      responses:
        "200":
          description: Reconcile accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RequestPaymentReconcileResponse"
        "401":
          description: Invalid or expired token
        "404":
          description: Request or payment session not found
        "409":
          description: Payment session is missing on request guard context, or provider does not support reconciliation
        "500":
          description: Reconcile misconfigured

  /v1/requests/{id}/response:
    get:
      summary: Get decrypted request response
      tags: [Requests]
      description: |
        Returns the decrypted tool result stored for a request.
        Primarily used by read-only widgets.
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Response found (or null if not completed)
          content:
            application/json:
              schema:
                type: object
                properties:
                  response:
                    description: Null when no response exists yet.
                    nullable: true
                    oneOf:
                      - type: object
                        properties:
                          status: { type: string }
                          result: { type: object }
                          createdAt: { type: string }

  /v1/requests/{id}/events:
    get:
      summary: List request events
      tags: [Requests]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Request events
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/RequestEvent" }

    post:
      summary: Append request events (publisher callback)
      tags: [Requests]
      description: |
        Allows an async publisher/job executor to append progress/events to an existing request.

        Auth: `Authorization: Bearer <publisher_callback_token>` (request-scoped).
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                type: { type: string, example: PUBLISHER_PROGRESS }
                message: { type: string, example: halfway }
                data: { type: object }
      responses:
        "201":
          description: Created

  /v1/requests/{id}/artifacts:
    get:
      summary: List request artifacts
      tags: [Requests]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
        - name: direction
          in: query
          schema: { type: string, enum: [input, output] }
          description: Filter by artifact direction (input or output). Defaults to output only.
      responses:
        "200":
          description: Request artifacts

    post:
      summary: Attach an input artifact to a request
      tags: [Requests]
      security:
        - BearerAuth: []
      description: |
        Promotes a temporary upload to a permanent input artifact on a request.

        Auth: `Authorization: Bearer <widgetToken>`.
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [direction, fieldPath, uploadId]
              additionalProperties: false
              properties:
                direction: { type: string, enum: [input] }
                fieldPath: { type: string }
                uploadId: { type: string }
      responses:
        "201":
          description: Artifact created
          content:
            application/json:
              schema:
                type: object
                properties:
                  artifact:
                    type: object
                    properties:
                      id: { type: string }
                      url: { type: string }

  /v1/requests/{id}/updates:
    get:
      summary: Stream real-time status updates (SSE)
      tags: [Requests]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
        - name: token
          in: query
          description: Widget token (if not using Bearer auth)
          schema: { type: string }
      responses:
        "200":
          description: SSE stream
          content:
            text/event-stream:
              schema: { type: string }

  /v1/requests/{id}/complete:
    post:
      summary: Complete a request (publisher callback)
      tags: [Requests]
      description: |
        Finalizes an async request by writing a response and setting the request status.
        Designed for the job model (publisher push). Must be idempotent.

        Auth: `Authorization: Bearer <publisher_callback_token>` (request-scoped).
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                status: { type: string, example: success }
                result: { type: object }
                message: { type: string }
      responses:
        "201":
          description: Completed
        "200":
          description: Already completed (idempotent)

  /v1/invoices/{id}/document:
    get:
      summary: Download or preview a generated invoice document
      tags: [Invoices]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
        - name: download
          in: query
          description: Force attachment download instead of inline preview.
          schema: { type: string, enum: ["1", "true"] }
      responses:
        "200":
          description: Generated invoice document
          content:
            text/html:
              schema: { type: string }
            application/pdf:
              schema: { type: string, format: binary }
        "403":
          description: Unauthorized to access the invoice document
        "404":
          description: Invoice or document not found

  /artifacts/{wildcard}:
    get:
      summary: Download an artifact file
      tags: [System]
      description: |
        Serves an artifact file from storage.
        Requires authentication (Widget token, User session, Publisher session, or API Key)
        matching the request that owns the artifact.
      parameters:
        - name: wildcard
          in: path
          required: true
          description: The storage key of the artifact (e.g. `artifacts/XYZ.pdf`)
          schema: { type: string }
        - name: token
          in: query
          description: Optional authentication token (alternative to Bearer auth)
          schema: { type: string }
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: The artifact file
          content:
            "*/*":
              schema: { type: string, format: binary }
        "403":
          description: Unauthorized
        "404":
          description: Artifact not found

  /v1/proxy/integrator/{wildcard}:
    get:
      summary: Proxy request to Integrator API
      tags: [System]
      description: |
        Proxies requests from a Widget or Publisher to the Partner/Integrator's private API.
        The Gateway validates scopes and injects tenant context.
        Predicate-scoped permissions are supported via callback token scopes, for example:
        - `read:integrator-projects?projectId=proj_123`
        - `read:integrator-projects?orgId=org_123`
        - `read:integrator-projects?userId=user_123`
        - `read:integrator-projects?resourceType=projects&resourceId=proj_123`
        - `read:integrator-profile?clientId=client_abc`
        - `read:integrator-profile?installationId=inst_123`
        - `read:integrator-profile?xappId=xapp_123`
        - `read:integrator-profile?method=GET&pathPrefix=/profile`
        Supported predicate keys: `method`, `pathPrefix`, `clientId`, `installationId`, `xappId`, `subjectId`,
        `orgId`, `userId`, `resourceType`, `resourceId`, `projectId`. Additional attributes can be resolved via
        predicate hooks.
      security:
        - BearerAuth: []
      parameters:
        - name: wildcard
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Successful proxy response
        "403":
          description: Scope not permitted or origin not allowed
          content:
            application/json:
              schema:
                type: object
                properties:
                  message: { type: string }
                  method: { type: string }
                  path: { type: string }
                  grantedScopes:
                    type: array
                    items: { type: string }
              examples:
                scopeDenied:
                  value:
                    message: Scope not permitted for this proxy route
                    method: GET
                    path: /profile
                    grantedScopes:
                      - read:integrator-profile?clientId=client_other
                installationScopeDenied:
                  value:
                    message: Scope not permitted for this proxy route
                    method: GET
                    path: /profile
                    grantedScopes:
                      - read:integrator-profile?installationId=inst_scoped_1
                xappScopeDenied:
                  value:
                    message: Scope not permitted for this proxy route
                    method: GET
                    path: /profile
                    grantedScopes:
                      - read:integrator-profile?xappId=xapp_scoped_1

    post:
      summary: Proxy request to Integrator API
      tags: [System]
      security:
        - BearerAuth: []
      parameters:
        - name: wildcard
          in: path
          required: true
          schema: { type: string }
      requestBody:
        content:
          application/json:
            schema: { type: object }
      responses:
        "200":
          description: Successful proxy response

    put:
      summary: Proxy request to Integrator API
      tags: [System]
      security:
        - BearerAuth: []
      parameters:
        - name: wildcard
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Successful proxy response

    patch:
      summary: Proxy request to Integrator API
      tags: [System]
      security:
        - BearerAuth: []
      parameters:
        - name: wildcard
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Successful proxy response

    delete:
      summary: Proxy request to Integrator API
      tags: [System]
      security:
        - BearerAuth: []
      parameters:
        - name: wildcard
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Successful proxy response

  /v1/auth/discover:
    get:
      summary: Discover available auth methods for a user or client
      tags: [User Portal]
      description: |
        Given an email or clientId, returns the available authentication methods
        (e.g. OIDC, platform OTP) so the frontend can display the correct login UI.
      parameters:
        - name: q
          in: query
          required: true
          schema: { type: string }
          description: Email address or clientId to discover auth methods for.
      responses:
        "200":
          description: Auth methods
          content:
            application/json:
              schema:
                type: object
                properties:
                  client:
                    type: object
                    nullable: true
                    properties:
                      id: { type: string }
                      slug: { type: string }
                      name: { type: string }
                  methods:
                    type: array
                    items:
                      type: object
                      properties:
                        type: { type: string, description: "oidc | platform_otp" }
                        label: { type: string }
                        platform_client_id: { type: string }
                        issuer: { type: string }
                        discovery_url: { type: string }
                        client_id: { type: string }
                        branding:
                          type: object
                          properties:
                            logo_url: { type: string, nullable: true }
                            primary_color: { type: string, nullable: true }
        "400":
          description: Missing query parameter

  /v1/auth/tenants:
    get:
      summary: List active tenants for login discovery
      tags: [User Portal]
      description: |
        Returns a lightweight list of active tenants for login and tenant-picking UIs.
        This route is intentionally public and only exposes display-safe tenant metadata.
      responses:
        "200":
          description: Active tenants
          content:
            application/json:
              schema:
                type: object
                properties:
                  tenants:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        name: { type: string }
                        slug: { type: string }
                        logo_url: { type: string, nullable: true }

  /v1/auth/system/start:
    post:
      summary: Start system user login/registration (send OTP code)
      tags: [System Auth]
      description: |
        Sends a 6-digit verification code to the provided email address.
        Used for system-level authentication (publishers, tenants, superadmin).
        In closed admin mode, unknown/unprovisioned login emails return generic success
        but no OTP is dispatched.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              additionalProperties: false
              properties:
                email: { type: string }
                purpose: { type: string, enum: [login, register] }
      responses:
        "200":
          description: OTP start accepted (generic success)
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
        "400":
          description: Invalid email format
        "404":
          description: Not found (registration disabled)
        "429":
          description: OTP request rate limit exceeded or resend cooldown active

  /v1/auth/system/capabilities:
    get:
      summary: Get system-auth runtime capabilities
      tags: [System Auth]
      description: |
        Returns runtime capability flags used by auth UIs to decide whether
        optional flows (like self-register) should be shown.
      responses:
        "200":
          description: Capabilities
          content:
            application/json:
              schema:
                type: object
                properties:
                  system_register_enabled: { type: boolean }

  /v1/auth/system/verify:
    post:
      summary: Verify system user OTP code and get session token
      tags: [System Auth]
      description: |
        Verifies OTP and issues a system-user session token.
        For superadmin/tenant-capable users, also sets an HttpOnly `xapps_superadmin_token` cookie
        used by the superadmin/tenant console UI.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, code]
              additionalProperties: false
              properties:
                email: { type: string }
                code: { type: string }
      responses:
        "200":
          description: Session token
          content:
            application/json:
              schema:
                type: object
                properties:
                  token: { type: string }
                  user:
                    type: object
                    properties:
                      id: { type: string }
                      email: { type: string }
                      role: { type: string }
                      roles:
                        type: array
                        items: { type: string }
        "400":
          description: Invalid request payload
        "401":
          description: Invalid or expired code
        "403":
          description: User has no active admin membership
        "404":
          description: User not found
        "429":
          description: Too many invalid code attempts

  /v1/auth/system/me:
    get:
      summary: Get current system user from session token
      tags: [System Auth]
      description: |
        Resolves the current system user from either:
        - `Authorization: Bearer <token>`
        - HttpOnly cookie `xapps_superadmin_token` (superadmin/tenant console flow)
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Current user
          content:
            application/json:
              schema:
                type: object
                properties:
                  user:
                    type: object
                    properties:
                      id: { type: string }
                      email: { type: string }
                      role: { type: string }
                      roles:
                        type: array
                        items: { type: string }
        "401":
          description: Invalid or expired token

  /v1/auth/system/publisher-switch:
    post:
      summary: Switch active publisher context for the current system-user session
      tags: [System Auth]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [targetPublisherId]
              additionalProperties: false
              properties:
                targetPublisherId: { type: string }
      responses:
        "200":
          description: Publisher context switched
        "401":
          description: Invalid or expired token
        "403":
          description: Caller does not have access to the requested publisher

  /v1/auth/system/logout:
    post:
      summary: Revoke current system-user session token
      tags: [System Auth]
      description: |
        Revokes the current bearer/cookie system session token and clears the
        `xapps_superadmin_token` HttpOnly cookie.
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Session revoked
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }

  /v1/auth/system/register:
    post:
      summary: Register a new system user (publisher or tenant)
      tags: [System Auth]
      description: |
        Creates a new system user after email verification.
        Also creates the associated publisher or tenant entity.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, code, role]
              additionalProperties: false
              properties:
                email: { type: string }
                code: { type: string }
                role: { type: string, enum: [publisher, tenant] }
                name: { type: string }
                slug: { type: string }
      responses:
        "200":
          description: Registration successful
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  message: { type: string }
        "400":
          description: User already exists or missing fields
        "401":
          description: Invalid or expired registration code
        "404":
          description: Not found (system registration disabled)

  /v1/auth/switch:
    post:
      summary: Switch user session to a different tenant
      tags: [User Portal]
      security:
        - BearerAuth: []
      description: |
        Issues a new session token scoped to the target tenant.
        Requires the user's email to be associated with the target tenant.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [targetClientId]
              additionalProperties: false
              properties:
                targetClientId: { type: string }
      responses:
        "200":
          description: New session token for target tenant
          content:
            application/json:
              schema:
                type: object
                properties:
                  token: { type: string }
        "401":
          description: Invalid session token
        "403":
          description: No access to target tenant

  /v1/auth/exchange:
    post:
      summary: Exchange IdP JWT for an Xapps user session token
      tags: [User Portal]
      description: |
        Exchange boundary for `JWT-from-IdP → PASETO`.

        - Input: `Authorization: Bearer <IdP JWT>`
        - Output: `token` (PASETO, `typ: "user"`)

        Notes:
        - This endpoint is enabled only when `IDP_JWKS_URL` or `IDP_JWK_JSON` is configured.
        - JWT validation may enforce `IDP_ISSUER` and/or `IDP_AUDIENCE` when configured.
      responses:
        "200":
          description: Session token
          content:
            application/json:
              schema:
                type: object
                properties:
                  token: { type: string }
        "401":
          description: Invalid IdP token
        "501":
          description: Exchange not configured

  /v1/auth/email/start:
    post:
      summary: Start email OTP login
      tags: [User Portal]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [clientId, email]
              additionalProperties: false
              properties:
                clientId: { type: string }
                email: { type: string }
      responses:
        "200":
          description: OTP initiated
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, description: "sent | code_created_email_not_sent" }
                  expiresAt: { type: string, format: date-time }
                  delivery:
                    type: object
                    properties:
                      skipped: { type: boolean }
                      provider: { type: string }
                      messageId: { type: string }
                    required: [skipped, provider]
        "400":
          description: Invalid email address
        "429":
          description: OTP request rate limit exceeded or resend cooldown active

  /v1/auth/email/verify:
    post:
      summary: Verify email OTP and get session token
      tags: [User Portal]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [clientId, email, code]
              additionalProperties: false
              properties:
                clientId: { type: string }
                email: { type: string }
                code: { type: string }
      responses:
        "200":
          description: Session token
          content:
            application/json:
              schema:
                type: object
                properties:
                  token: { type: string }
        "400":
          description: Invalid, expired, or already-used OTP code

  /v1/me/requests:
    get:
      summary: List my requests
      tags: [User Portal]
      security:
        - BearerAuth: []
      parameters:
        - name: xappId
          in: query
          schema: { type: string }
        - name: toolName
          in: query
          schema: { type: string }
        - name: createdFrom
          in: query
          schema: { type: string, format: date }
        - name: createdTo
          in: query
          schema: { type: string, format: date }
        - name: page
          in: query
          schema: { type: integer, minimum: 1 }
        - name: pageSize
          in: query
          schema: { type: integer, minimum: 1, maximum: 100 }
      responses:
        "200":
          description: Requests list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        status: { type: string }
                        tool_name: { type: string }
                        guard_summary:
                          $ref: "#/components/schemas/GuardSummary"
                        usage_credit:
                          allOf:
                            - $ref: "#/components/schemas/PaymentUsageCreditSummary"
                          nullable: true
                        ops_attention:
                          allOf:
                            - $ref: "#/components/schemas/RequestOpsAttention"
                          nullable: true
              examples:
                requestsListWithGuardSummary:
                  value:
                    items:
                      - id: req_user_01
                        status: REJECTED
                        tool_name: submit_wizard_application
                        guard_summary:
                          trigger: "before:tool_run"
                          outcome: blocked
                          slug: owner-payment-gate
                          reason: payment_not_settled
                          monetization_state:
                            entitlement_state: unknown
                            balance_state: unknown
                            payment_attempt_state: failed
                            issuer: tenant
                            authority_lane: owner_managed
                            source_ref: tenant-policy/default
                          action:
                            kind: complete_payment

  /v1/me/requests/{id}:
    get:
      summary: Get my request details
      tags: [User Portal]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Request details
          content:
            application/json:
              schema:
                type: object
                properties:
                  request:
                    type: object
                    properties:
                      id: { type: string }
                      payload:
                        type: object
                        additionalProperties: true
                      guard_summary:
                        $ref: "#/components/schemas/GuardSummary"
                      usage_credit:
                        allOf:
                          - $ref: "#/components/schemas/PaymentUsageCreditSummary"
                        nullable: true
                      ops_attention:
                        allOf:
                          - $ref: "#/components/schemas/RequestOpsAttention"
                        nullable: true
                  events:
                    type: array
                    items: { type: object, additionalProperties: true }
                  response:
                    nullable: true
                  tool:
                    nullable: true
                  widgets:
                    type: array
                    items: { type: object, additionalProperties: true }
                  artifacts:
                    type: array
                    items: { type: object, additionalProperties: true }
              examples:
                blockedRequestWithGuardSummary:
                  summary: "Request detail exposes redacted guard summary projection"
                  value:
                    request:
                      id: req_01
                      guard_summary:
                        trigger: "before:tool_run"
                        outcome: blocked
                        slug: owner-payment-gate
                        reason: payment_not_settled
                        monetization_state:
                          entitlement_state: unknown
                          balance_state: unknown
                          payment_attempt_state: failed
                          issuer: tenant
                          authority_lane: owner_managed
                          source_ref: tenant-policy/default
                        action:
                          kind: complete_payment
                    events: []
                    response: null
                    tool: null
                    widgets: []
                    artifacts: []

  /v1/me/requests/{id}/payment/reconcile:
    post:
      summary: Reconcile payment finality for my request
      tags: [User Portal]
      x-compatibility-class: A
      security:
        - BearerAuth: []
      description: |
        Attempts provider-backed payment finality reconciliation for a request-level lock recovery flow.
        Intended for paid-but-not-reflected recovery (`complete_payment` action remains blocked).
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RequestPaymentReconcileRequest"
      responses:
        "200":
          description: Reconcile accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RequestPaymentReconcileResponse"
              examples:
                reconciledPaid:
                  summary: Provider settled and gateway produced signed redirect
                  value:
                    status: ok
                    payment_session_id: pay_01hxyzreconcile
                    provider_status: paid
                    finality_state: confirmed_paid
                    reconciled: true
                    redirect_url: "https://host.example/return?xapps_payment_status=paid&xapps_payment_session_id=pay_01hxyzreconcile&xapps_payment_sig=abc123&xapps_payment_sig_kid=gw-k1"
                stillPending:
                  summary: Provider remains pending; request stays blocked until settlement completes
                  value:
                    status: ok
                    payment_session_id: pay_01hxyzreconcile
                    provider_status: pending
                    finality_state: still_pending
                    reconciled: true
                    redirect_url: null
        "401":
          description: Session token required or expired
        "404":
          description: Request or payment session not found
        "409":
          description: Payment session is missing on request guard context, or provider does not support reconciliation
        "500":
          description: Reconcile misconfigured
        "502":
          description: Provider returned invalid contract payload

  /v1/me/invoices:
    get:
      summary: List my invoices
      tags: [User Portal]
      security:
        - BearerAuth: []
      parameters:
        - name: requestId
          in: query
          schema: { type: string }
        - name: paymentSessionId
          in: query
          schema: { type: string }
        - name: xappId
          in: query
          schema: { type: string }
        - name: installationId
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string }
        - name: ownerScope
          in: query
          schema: { type: string }
        - name: search
          in: query
          schema: { type: string }
        - name: createdFrom
          in: query
          schema: { type: string, format: date }
        - name: createdTo
          in: query
          schema: { type: string, format: date }
        - name: page
          in: query
          schema: { type: integer, minimum: 1 }
        - name: pageSize
          in: query
          schema: { type: integer, minimum: 1, maximum: 100 }
      responses:
        "200":
          description: Invoice list
          content:
            application/json:
              schema:
                type: object
                required: [items]
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/PortalInvoiceSummary"
                  pagination:
                    type: object
                    properties:
                      total: { type: integer }
                      page: { type: integer }
                      pageSize: { type: integer }
                      totalPages: { type: integer }
        "401":
          description: Session token required or expired

  /v1/me/invoices/{id}/history:
    get:
      summary: Get my invoice lifecycle history
      tags: [User Portal]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Invoice lifecycle history
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InvoiceHistoryResponse"
        "401":
          description: Session token required or expired
        "404":
          description: Invoice not found

  /v1/me/payment-sessions:
    get:
      summary: List my payment sessions
      tags: [User Portal]
      security:
        - BearerAuth: []
      parameters:
        - name: paymentSessionId
          in: query
          schema: { type: string }
        - name: xappId
          in: query
          schema: { type: string }
        - name: installationId
          in: query
          schema: { type: string }
        - name: toolName
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string }
        - name: issuer
          in: query
          schema: { type: string }
        - name: createdFrom
          in: query
          schema: { type: string, format: date }
        - name: createdTo
          in: query
          schema: { type: string, format: date }
        - name: limit
          in: query
          schema: { type: integer }
      responses:
        "200":
          description: Payment session list
          content:
            application/json:
              schema:
                type: object
                required: [items]
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/PortalPaymentSessionSummary"
        "401":
          description: Session token required or expired

  /v1/me/payment-sessions/{id}:
    get:
      summary: Get my payment session details
      tags: [User Portal]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Payment session details
          content:
            application/json:
              schema:
                type: object
                required: [session]
                properties:
                  session:
                    $ref: "#/components/schemas/PortalPaymentSessionSummary"
        "401":
          description: Session token required or expired
        "404":
          description: Payment session not found

  /v1/me/notifications:
    get:
      summary: List my notification inbox activity
      tags: [User Portal]
      security:
        - BearerAuth: []
      parameters:
        - name: notificationId
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string }
        - name: trigger
          in: query
          schema: { type: string }
        - name: xappId
          in: query
          schema: { type: string }
        - name: installationId
          in: query
          schema: { type: string }
        - name: unread
          in: query
          schema: { type: boolean }
        - name: limit
          in: query
          schema: { type: integer }
      responses:
        "200":
          description: Notification inbox activity and current preferences
          content:
            application/json:
              schema:
                type: object
                required: [items, unread_count, preferences]
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/PortalNotificationRecordSummary"
                  unread_count:
                    type: integer
                  preferences:
                    $ref: "#/components/schemas/PortalNotificationPreferences"
        "401":
          description: Session token required or expired

  /v1/me/notifications/read-all:
    post:
      summary: Mark all notification inbox items as read
      tags: [User Portal]
      security:
        - BearerAuth: []
      responses:
        "200":
          description: All notification inbox items marked as read
          content:
            application/json:
              schema:
                type: object
                required: [ok, unread_count]
                properties:
                  ok: { type: boolean }
                  unread_count: { type: integer }
        "401":
          description: Session token required or expired

  /v1/me/notifications/{notificationId}/read:
    post:
      summary: Mark one notification inbox item as read
      tags: [User Portal]
      security:
        - BearerAuth: []
      parameters:
        - name: notificationId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Notification inbox item marked as read
          content:
            application/json:
              schema:
                type: object
                required: [ok, unread_count]
                properties:
                  ok: { type: boolean }
                  unread_count: { type: integer }
        "401":
          description: Session token required or expired
        "404":
          description: Notification not found

  /v1/me/notification-preferences:
    post:
      summary: Update my notification preferences
      tags: [User Portal]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [preferences]
              properties:
                preferences:
                  $ref: "#/components/schemas/PortalNotificationPreferences"
      responses:
        "200":
          description: Notification preferences updated
          content:
            application/json:
              schema:
                type: object
                required: [ok, preferences]
                properties:
                  ok: { type: boolean }
                  preferences:
                    $ref: "#/components/schemas/PortalNotificationPreferences"
        "401":
          description: Session token required or expired

  /v1/me/preferences:
    get:
      summary: Get my portal preferences
      tags: [User Portal]
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Portal preferences
          content:
            application/json:
              schema:
                type: object
                required: [preferences]
                properties:
                  preferences:
                    $ref: "#/components/schemas/PortalPreferences"
        "401":
          description: Session token required or expired
    post:
      summary: Update my portal preferences
      tags: [User Portal]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [preferences]
              properties:
                preferences:
                  $ref: "#/components/schemas/PortalPreferences"
      responses:
        "200":
          description: Portal preferences updated
          content:
            application/json:
              schema:
                type: object
                required: [ok, preferences]
                properties:
                  ok: { type: boolean }
                  preferences:
                    $ref: "#/components/schemas/PortalPreferences"
        "401":
          description: Session token required or expired

  /v1/me/subject-profiles:
    get:
      summary: List my saved subject profiles
      tags: [User Portal]
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Subject profile list
          content:
            application/json:
              schema:
                type: object
                required: [active_subject_id, items]
                properties:
                  active_subject_id:
                    type: string
                    nullable: true
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/PortalSubjectProfileSummary"
        "401":
          description: Session token required or expired

  /v1/me/subject-profiles/default:
    post:
      summary: Mark one saved subject profile as default
      tags: [User Portal]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [subject_id, candidate_id]
              properties:
                subject_id: { type: string }
                candidate_id: { type: string }
      responses:
        "200":
          description: Default subject profile updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
        "400":
          description: subject_id and candidate_id are required
        "401":
          description: Session token required or expired
        "404":
          description: Subject profile or candidate not found

  /v1/me/subject-profiles/guard-ui:
    get:
      summary: Get proactive subject-profile chooser guard descriptor
      tags: [User Portal]
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Subject-profile chooser descriptor
          content:
            application/json:
              schema:
                type: object
                required: [xappId, guardUi]
                properties:
                  xappId: { type: string }
                  guardUi:
                    $ref: "#/components/schemas/SubjectProfileGuardUiDescriptor"
        "400":
          description: Guard snapshot resolution failed
        "401":
          description: Session token required or expired
        "404":
          description: Subject profile or guard app not found

  /v1/me/subject-profiles/apply:
    post:
      summary: Apply subject-profile chooser payload to my saved private catalog
      tags: [User Portal]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                payload:
                  type: object
                  additionalProperties: true
      responses:
        "200":
          description: Subject profile payload applied
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
        "400":
          description: Subject profile payload did not satisfy the chooser policy
        "401":
          description: Session token required or expired
        "404":
          description: Subject profile not found

  /v1/me/subject-profiles/{subjectId}/{candidateId}:
    delete:
      summary: Delete a saved subject profile candidate
      tags: [User Portal]
      security:
        - BearerAuth: []
      parameters:
        - name: subjectId
          in: path
          required: true
          schema: { type: string }
        - name: candidateId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Profile candidate deleted
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
        "400":
          description: Missing subjectId or candidateId
        "401":
          description: Session token required or expired
        "404":
          description: Subject or profile candidate not found

  /v1/me/subject-profiles/history:
    get:
      summary: List subject profile change history
      tags: [User Portal]
      security:
        - BearerAuth: []
      parameters:
        - name: limit
          in: query
          required: false
          schema: { type: integer, default: 20, minimum: 1, maximum: 100 }
      responses:
        "200":
          description: Profile history records
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        candidate_id: { type: string, nullable: true }
                        event_type: { type: string, nullable: true }
                        profile_family: { type: string, nullable: true }
                        source: { type: string, nullable: true }
                        created_at: { type: string, format: date-time }
        "401":
          description: Session token required or expired

  /v1/publisher/links/complete:
    post:
      summary: Complete publisher↔subject link (publisher → platform)
      tags: [Publisher Links]
      description: |
        Creates or updates the link between a platform `subjectId` and a stable publisher user identity.

        Auth: `Authorization: X-API-Key <publisherApiKey>`.
      parameters:
        - name: Authorization
          in: header
          required: true
          schema: { type: string }
          description: Must be of the form `X-API-Key <publisherApiKey>`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [subjectId, xappId, publisherUserId]
              additionalProperties: false
              properties:
                subjectId: { type: string }
                xappId: { type: string }
                publisherUserId: { type: string }
                realm: { type: string }
                metadata:
                  type: object
                  additionalProperties: true
      responses:
        "200":
          description: Link completed
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                properties:
                  success: { type: boolean }

  /v1/publisher/links/revoke:
    post:
      summary: Revoke publisher↔subject link (publisher → platform)
      tags: [Publisher Links]
      description: |
        Revokes a previously completed publisher link.

        Auth: `Authorization: X-API-Key <publisherApiKey>`.
      parameters:
        - name: Authorization
          in: header
          required: true
          schema: { type: string }
          description: Must be of the form `X-API-Key <publisherApiKey>`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [subjectId, xappId, publisherUserId]
              additionalProperties: false
              properties:
                subjectId: { type: string }
                xappId: { type: string }
                publisherUserId: { type: string }
                reason: { type: string }
      responses:
        "200":
          description: Link revoked
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                properties:
                  revoked: { type: boolean }
                  alreadyRevoked: { type: boolean }
                  deleted: { type: number }

  /v1/publisher/links/status:
    get:
      summary: Get link status (widget)
      tags: [Publisher Links]
      description: |
        Returns whether the current widget subject is linked to the publisher identity for the current xapp.

        Auth: `Authorization: Bearer <widgetToken>`.
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Link status
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                properties:
                  linked: { type: boolean }
                  reason: { type: string }
                  publisherUserId: { type: string }

  /v1/subject/links/revoke:
    post:
      summary: Revoke link (subject/widget)
      tags: [Publisher Links]
      description: |
        Revokes the current subject’s link for an xapp.

        Auth: `Authorization: Bearer <widgetToken>`.
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [xappId]
              additionalProperties: false
              properties:
                xappId: { type: string }
                reason: { type: string }
      responses:
        "200":
          description: Link revoked
          content:
            application/json:
              schema:
                type: object
                additionalProperties: false
                properties:
                  revoked: { type: boolean }
                  alreadyRevoked: { type: boolean }
                  deleted: { type: number }

  /v1/publisher/profile:
    get:
      summary: Get publisher profile
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Publisher profile
          content:
            application/json:
              schema:
                type: object
                properties:
                  publisher:
                    type: object
                    properties:
                      id: { type: string }
                      name: { type: string }
                      slug: { type: string }
                      type: { type: string }
                      client_id: { type: string, nullable: true }
                      status: { type: string }
                      logo_url: { type: string, nullable: true }
                      details_jsonb: { type: object, nullable: true }
                      payment_ui:
                        type: object
                        properties:
                          brand:
                            type: object
                            properties:
                              name: { type: string }
                              logo_url: { type: string }
                              accent: { type: string }
                              mode: { type: string }
                          copy:
                            type: object
                            properties:
                              title: { type: string }
                              subtitle: { type: string }
                      payment_provider_credentials_refs:
                        type: object
                        additionalProperties:
                          type: object
                          additionalProperties: { type: string }
                      payment_provider_secret_refs:
                        type: object
                        additionalProperties:
                          type: object
                          additionalProperties: { type: string }
                      notification_ui:
                        $ref: "#/components/schemas/NotificationUiContract"
                      notification_provider_credentials_refs:
                        $ref: "#/components/schemas/ProviderCredentialRefsContract"
                      notification_provider_secret_refs:
                        $ref: "#/components/schemas/ProviderCredentialRefsContract"
                      notification_template_overrides:
                        $ref: "#/components/schemas/NotificationTemplateOverridesContract"
                      invoice_ui:
                        $ref: "#/components/schemas/InvoiceUiContract"
                      invoice_provider_credentials_refs:
                        $ref: "#/components/schemas/InvoiceProviderRefsContract"
                      invoice_provider_secret_refs:
                        $ref: "#/components/schemas/InvoiceProviderRefsContract"
                      invoice_template_overrides:
                        $ref: "#/components/schemas/InvoiceTemplateOverridesContract"
                      created_at: { type: string }
              example:
                publisher:
                  id: pub_123
                  name: Acme Publisher
                  slug: acme
                  type: independent
                  client_id: null
                  status: active
                  logo_url: https://cdn.acme.example/logo.svg
                  details_jsonb:
                    payment_ui:
                      brand:
                        name: Acme Checkout
                        logo_url: https://cdn.acme.example/checkout-logo.svg
                        accent: "#0f172a"
                        mode: light
                      copy:
                        title: Complete Payment
                        subtitle: Secure publisher checkout
                    payment_provider_credentials_refs:
                      stripe:
                        bundle_ref: platform://payment:publisher:stripe:bundle?scope=publisher&scope_id=pub_123
                  payment_ui:
                    brand:
                      name: Acme Checkout
                      logo_url: https://cdn.acme.example/checkout-logo.svg
                      accent: "#0f172a"
                      mode: light
                    copy:
                      title: Complete Payment
                      subtitle: Secure publisher checkout
                  payment_provider_credentials_refs:
                    stripe:
                      bundle_ref: platform://payment:publisher:stripe:bundle?scope=publisher&scope_id=pub_123
                  payment_provider_secret_refs:
                    stripe:
                      bundle_ref: platform://payment:publisher:stripe:bundle?scope=publisher&scope_id=pub_123
                  notification_ui:
                    email:
                      from_email: updates@acme.example
                      from_name: Acme Updates
                      provider_key: mailgun
                  notification_provider_credentials_refs:
                    mailgun:
                      bundle_ref: platform://notification:publisher:mailgun:bundle?scope=publisher&scope_id=pub_123
                  notification_template_overrides:
                    templates:
                      publisher_request_created_template:
                        subject: We received your request
                        text: Your request is now in progress.
                        html: "<p>Your request is now in progress.</p>"
                  invoice_ui:
                    provider_key: smartbill
                  invoice_provider_credentials_refs:
                    smartbill:
                      bundle_ref: platform://invoice:publisher:smartbill:bundle?scope=publisher&scope_id=pub_123
                  invoice_template_overrides:
                    templates:
                      publisher_response_invoice_template:
                        title: Publisher response invoice
                        text: Invoice for your completed response.
                        html: "<div>Invoice for your completed response.</div>"
                  created_at: "2026-03-01T10:00:00.000Z"

    patch:
      summary: Update publisher profile
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                name: { type: string }
                logoUrl: { type: string }
                client_id: { type: string }
                defaultClientId: { type: string }
                payment_ui:
                  type: object
                  properties:
                    brand:
                      type: object
                      properties:
                        name: { type: string }
                        logo_url: { type: string }
                        accent: { type: string }
                        mode: { type: string }
                    copy:
                      type: object
                      properties:
                        title: { type: string }
                        subtitle: { type: string }
                payment_provider_credentials_refs:
                  $ref: "#/components/schemas/ProviderCredentialRefsContract"
                payment_provider_secret_refs:
                  $ref: "#/components/schemas/ProviderCredentialRefsContract"
                notification_ui:
                  $ref: "#/components/schemas/NotificationUiContract"
                notification_provider_credentials_refs:
                  $ref: "#/components/schemas/ProviderCredentialRefsContract"
                notification_provider_secret_refs:
                  $ref: "#/components/schemas/ProviderCredentialRefsContract"
                notification_template_overrides:
                  $ref: "#/components/schemas/NotificationTemplateOverridesContract"
                invoice_ui:
                  $ref: "#/components/schemas/InvoiceUiContract"
                invoice_provider_credentials_refs:
                  $ref: "#/components/schemas/InvoiceProviderRefsContract"
                invoice_provider_secret_refs:
                  $ref: "#/components/schemas/InvoiceProviderRefsContract"
                invoice_template_overrides:
                  $ref: "#/components/schemas/InvoiceTemplateOverridesContract"
            example:
              payment_ui:
                brand:
                  name: Acme Checkout
                  logo_url: https://cdn.acme.example/checkout-logo.svg
                  accent: "#0f172a"
                  mode: light
                copy:
                  title: Complete Payment
                  subtitle: Secure publisher checkout
              payment_provider_credentials_refs:
                stripe:
                  bundle_ref: platform://payment:publisher:stripe:bundle?scope=publisher&scope_id=pub_123
                paypal:
                  bundle_ref: platform://payment:publisher:paypal:bundle?scope=publisher&scope_id=pub_123
      responses:
        "200":
          description: Profile updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  publisher:
                    type: object
                    properties:
                      id: { type: string }
                      name: { type: string }
                      slug: { type: string }
                      type: { type: string }
                      client_id: { type: string, nullable: true }
                      status: { type: string }
                      logo_url: { type: string, nullable: true }
                      details_jsonb: { type: object, nullable: true }
                      payment_ui:
                        type: object
                        properties:
                          brand:
                            type: object
                            properties:
                              name: { type: string }
                              logo_url: { type: string }
                              accent: { type: string }
                              mode: { type: string }
                          copy:
                            type: object
                            properties:
                              title: { type: string }
                              subtitle: { type: string }
                      payment_provider_credentials_refs:
                        type: object
                        additionalProperties:
                          type: object
                          additionalProperties: { type: string }
                      payment_provider_secret_refs:
                        $ref: "#/components/schemas/ProviderCredentialRefsContract"
                      notification_ui:
                        $ref: "#/components/schemas/NotificationUiContract"
                      notification_provider_credentials_refs:
                        $ref: "#/components/schemas/ProviderCredentialRefsContract"
                      notification_provider_secret_refs:
                        $ref: "#/components/schemas/ProviderCredentialRefsContract"
                      notification_template_overrides:
                        $ref: "#/components/schemas/NotificationTemplateOverridesContract"
                      created_at: { type: string }
              example:
                publisher:
                  id: pub_123
                  name: Acme Publisher
                  slug: acme
                  type: independent
                  client_id: null
                  status: active
                  logo_url: https://cdn.acme.example/logo.svg
                  details_jsonb:
                    payment_ui:
                      brand:
                        name: Acme Checkout
                        logo_url: https://cdn.acme.example/checkout-logo.svg
                        accent: "#0f172a"
                        mode: light
                      copy:
                        title: Complete Payment
                        subtitle: Secure publisher checkout
                    payment_provider_credentials_refs:
                      stripe:
                        bundle_ref: platform://payment:publisher:stripe:bundle?scope=publisher&scope_id=pub_123
                      paypal:
                        bundle_ref: platform://payment:publisher:paypal:bundle?scope=publisher&scope_id=pub_123
                  payment_ui:
                    brand:
                      name: Acme Checkout
                      logo_url: https://cdn.acme.example/checkout-logo.svg
                      accent: "#0f172a"
                      mode: light
                    copy:
                      title: Complete Payment
                      subtitle: Secure publisher checkout
                  payment_provider_credentials_refs:
                    stripe:
                      bundle_ref: platform://payment:publisher:stripe:bundle?scope=publisher&scope_id=pub_123
                    paypal:
                      bundle_ref: platform://payment:publisher:paypal:bundle?scope=publisher&scope_id=pub_123
                  payment_provider_secret_refs:
                    stripe:
                      bundle_ref: platform://payment:publisher:stripe:bundle?scope=publisher&scope_id=pub_123
                  notification_ui:
                    email:
                      from_email: updates@acme.example
                      from_name: Acme Updates
                      provider_key: mailgun
                  notification_provider_credentials_refs:
                    mailgun:
                      bundle_ref: platform://notification:publisher:mailgun:bundle?scope=publisher&scope_id=pub_123
                  notification_template_overrides:
                    templates:
                      publisher_request_created_template:
                        subject: We received your request
                        text: Your request is now in progress.
                        html: "<p>Your request is now in progress.</p>"
                  created_at: "2026-03-01T10:00:00.000Z"

  /v1/publisher/secrets:
    get:
      summary: List publisher-scoped platform secrets
      tags: [Publisher Console]
      security:
        - BearerAuth: []
        - ApiKeyAuth: []
      parameters:
        - name: tag
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string }
      responses:
        "200":
          description: OK
    post:
      summary: Create publisher-scoped platform secret
      tags: [Publisher Console]
      security:
        - BearerAuth: []
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
                tags:
                  type: array
                  items: { type: string }
                secret: { type: string }
                secretRef: { type: string }
                description: { type: string }
                expiresAt: { type: string, format: date-time }
      responses:
        "201":
          description: Created

  /v1/publisher/secrets/{id}:
    get:
      summary: Get publisher-scoped platform secret
      tags: [Publisher Console]
      security:
        - BearerAuth: []
        - ApiKeyAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: OK
    patch:
      summary: Update publisher-scoped platform secret
      tags: [Publisher Console]
      security:
        - BearerAuth: []
        - ApiKeyAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
                tags:
                  type: array
                  items: { type: string }
                secret: { type: string }
                secretRef: { type: string }
                description: { type: string }
                status: { type: string }
                expiresAt: { type: string, format: date-time, nullable: true }
      responses:
        "200":
          description: Updated
    delete:
      summary: Delete publisher-scoped platform secret
      tags: [Publisher Console]
      security:
        - BearerAuth: []
        - ApiKeyAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: OK

  /v1/publisher/bridge/token:
    post:
      summary: Exchange portal token for vendor assertion
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      description: |
        Exchanges a portal session token for a short-lived vendor assertion that
        can be consumed by publisher backends.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [publisher_id]
              additionalProperties: false
              properties:
                publisher_id: { type: string }
                scopes:
                  type: array
                  items: { type: string }
                link_required: { type: boolean }
      responses:
        "200":
          description: Assertion minted
          content:
            application/json:
              schema:
                type: object
                properties:
                  vendor_assertion: { type: string }
                  issuer: { type: string, example: xapps }
                  subject_id: { type: string }
                  link_id: { type: string }
                  expires_in: { type: integer }
        "409":
          description: Linking required
          content:
            application/json:
              schema:
                type: object
                properties:
                  message: { type: string }
                  code: { type: string, example: NEEDS_LINKING }
                  setup_url: { type: string, nullable: true }

  /v1/publisher/gateway-keys:
    get:
      summary: List Gateway verification key metadata
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      description: |
        Returns key metadata for gateway-issued vendor assertions.
        Public key material is null for symmetric (`paseto.v4.local`) keys.
      responses:
        "200":
          description: Gateway keys
          content:
            application/json:
              schema:
                type: object
                properties:
                  keys:
                    type: array
                    items:
                      type: object
                      properties:
                        kid: { type: string }
                        alg: { type: string }
                        use: { type: string }
                        public_key: { type: string, nullable: true }
                        expires_at: { type: string }
                        status: { type: string }

  /v1/publisher/subject-keys/{keyId}:
    get:
      summary: Get subject key details (publisher scope)
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      description: Returns detailed subject key info including usage statistics and recent requests.
      parameters:
        - name: keyId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Subject key details

  /v1/publisher/installations/{installationId}/subject-keys:
    get:
      summary: List subject keys for an installation (publisher scope)
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: installationId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Subject keys list

    post:
      summary: Register a subject key (publisher scope)
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: installationId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [subjectId, algorithm, publicKey]
              additionalProperties: false
              properties:
                subjectId: { type: string }
                algorithm: { type: string, enum: [ed25519] }
                publicKey: { type: string }
      responses:
        "201":
          description: Key registered

  /v1/publisher/installations/{installationId}/subject-keys/{keyId}:
    get:
      summary: Get subject key details for an installation (publisher scope)
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: installationId
          in: path
          required: true
          schema: { type: string }
        - name: keyId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Subject key details

    delete:
      summary: Revoke a subject key (publisher scope)
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: installationId
          in: path
          required: true
          schema: { type: string }
        - name: keyId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Key revoked

  /v1/publisher/installations/{id}:
    get:
      summary: Get a single publisher installation
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Installation details

    patch:
      summary: Update a publisher installation
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                publisher_api_enabled: { type: boolean }
      responses:
        "200":
          description: Installation updated

  /v1/publisher/clients:
    get:
      summary: List publisher clients
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Clients list

  /v1/publisher/tenant-links:
    get:
      summary: List publisher tenant onboarding links
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Publisher tenant links

  /v1/publisher/tenant-links/onboarding-request:
    post:
      summary: Request publisher onboarding for a tenant (auto-approved in current implementation)
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [client_id]
              additionalProperties: false
              properties:
                client_id: { type: string }
      responses:
        "201":
          description: Onboarding request created and approved

  /v1/publisher/ip-allowlist:
    get:
      summary: List publisher IP allowlist CIDRs
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      description: |
        Lists CIDRs used for optional callback IP allowlisting.

        Auth: `Authorization: Bearer <publisherSession>` (PASETO, `typ: "publisher"`).
      responses:
        "200":
          description: Allowlist
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
                      properties:
                        cidr: { type: string }

    post:
      summary: Add a CIDR to the publisher IP allowlist
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [cidr]
              additionalProperties: false
              properties:
                cidr: { type: string }
      responses:
        "201":
          description: Added

    delete:
      summary: Remove a CIDR from the publisher IP allowlist
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [cidr]
              additionalProperties: false
              properties:
                cidr: { type: string }
      responses:
        "200":
          description: Deleted

  /v1/publisher/widget-sessions:
    post:
      summary: Mint a publisher widget-scoped token (server-to-server)
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      description: |
        Issues a short-lived token that grants the same permissions as a widget token,
        but for publisher server-to-server API usage.

        Auth: `Authorization: Bearer <publisherSession>` (PASETO, `typ: "publisher"`).

        The minted token has `typ: "publisher_widget"` and is scoped to:
        - `installationId`
        - `widgetId`
        - optional `subjectId`
        - optional `requestId`

        Constraints:
        - Publisher must own the widget (via xapp publisher ownership).
        - Installation must exist and be active.
        - Installation may disable publisher access via `installations.publisher_api_enabled=false`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [installationId, widgetId]
              additionalProperties: false
              properties:
                installationId: { type: string }
                widgetId: { type: string }
                subjectId: { type: string }
                requestId: { type: string }
      responses:
        "200":
          description: Token minted
          content:
            application/json:
              schema:
                type: object
                properties:
                  token: { type: string }

  /v1/publisher/predicate-scopes/validate:
    post:
      summary: Validate predicate scopes
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      description: |
        Validates predicate scope syntax and optionally evaluates a sample request context against
        the integrator proxy rules. Useful for publishers authoring predicate scope profiles.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PredicateScopeValidationRequest"
      responses:
        "200":
          description: Validation results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PredicateScopeValidationResponse"

  /v1/publisher/import-manifest:
    post:
      summary: Import Xapp manifest
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/Manifest" }
            examples:
              guardedPolicyPack:
                summary: Manifest with additive before:tool_run policy-pack guard
                value:
                  name: Guarded Insights
                  slug: guarded-insights
                  version: 1.2.0
                  guards:
                    - slug: tenant-entitlement-v2
                      trigger: "before:tool_run"
                      headless: true
                      config:
                        policy:
                          kind: subscription
                          pass_if_any: [pro, enterprise]
                          fail_if_any: [suspended]
                        reason: tier_required
                        message: Subscription tier required
                        tools: [deep_analysis]
                  guards_policy:
                    "before:tool_run": all
                  tools:
                    - tool_name: deep_analysis
                      title: Deep Analysis
                      input_schema:
                        type: object
                        properties:
                          subjectId:
                            type: string
                      output_schema:
                        type: object
                        properties:
                          score:
                            type: number
                  widgets:
                    - widget_name: main
                      type: read
                      renderer: platform
                      bind_tool_name: deep_analysis
      responses:
        "201":
          description: Manifest imported

  /v1/publisher/import-openapi:
    post:
      summary: Generate Xapp manifest draft from OpenAPI
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                openapiText: { type: string }
                openapiUrl: { type: string }
                name: { type: string }
                slug: { type: string }
                version: { type: string }
                description: { type: string }
                endpointBaseUrl: { type: string }
                endpointEnv: { type: string }
      responses:
        "200":
          description: Manifest draft generated
          content:
            application/json:
              schema:
                type: object
                properties:
                  manifest: { type: object }
                  metadata:
                    type: object
                    properties:
                      source: { type: string }
                      detectedTitle: { type: string }
                      detectedVersion: { type: string }

  /v1/publisher/xapps:
    get:
      summary: List publisher xapps
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Xapps list

    post:
      summary: Create a publisher xapp
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, slug]
              additionalProperties: false
              properties:
                name: { type: string }
                slug: { type: string }
                description: { type: string }
                visibility: { type: string }
                status:
                  type: string
                  enum: [active, disabled, archived]
                metadata: { type: object }
      responses:
        "201":
          description: Xapp created

  /v1/publisher/xapps/{xappId}:
    get:
      summary: Get publisher xapp
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Xapp

    patch:
      summary: Update publisher xapp
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                name: { type: string }
                slug: { type: string }
                description: { type: string }
                visibility: { type: string }
                status: { type: string }
                metadata: { type: object }
      responses:
        "200":
          description: Xapp updated

  /v1/publisher/xapps/{xappId}/versions:
    get:
      summary: List versions for a publisher xapp
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Versions list

    post:
      summary: Create a draft xapp version
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [version]
              additionalProperties: false
              properties:
                version: { type: string }
                manifest: { $ref: "#/components/schemas/Manifest" }
      responses:
        "201":
          description: Draft version created

  /v1/publisher/xapp-versions/{xappVersionId}/manifest:
    get:
      summary: Get stored manifest for an xapp version
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: xappVersionId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Manifest

    put:
      summary: Update draft manifest for an xapp version
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: xappVersionId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/Manifest" }
      responses:
        "200":
          description: Manifest updated

  /v1/publisher/xapp-versions/{xappVersionId}/publish:
    post:
      summary: Publish an xapp version
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: xappVersionId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Published

  /v1/publisher/xapp-versions/{xappVersionId}/archive:
    post:
      summary: Archive an xapp version
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: xappVersionId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Archived

  /v1/publisher/xapp-versions/{xappVersionId}/endpoints:
    get:
      summary: List endpoints for an xapp version
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: xappVersionId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Endpoints list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/XappEndpoint" }

    post:
      summary: Create endpoint for an xapp version
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: xappVersionId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [env, baseUrl, timeoutMs]
              additionalProperties: false
              properties:
                env: { type: string }
                baseUrl: { type: string }
                timeoutMs: { type: number }
                healthCheck:
                  type: object
                  additionalProperties: false
                  properties:
                    path: { type: string }
                    intervalS: { type: number }
                retryPolicy:
                  type: object
                  additionalProperties: false
                  properties:
                    maxRetries: { type: number }
                    backoff: { type: string, enum: [none, linear, exponential] }
                region: { type: string }
                labels:
                  type: object
                  additionalProperties: { type: string }
      responses:
        "201":
          description: Endpoint created

  /v1/publisher/tenants:
    get:
      summary: List all active tenants
      tags: [Publisher Console]
      description: |
        Returns a list of all active tenants (clients) in the system.
        This allows publishers to see potential targets for their applications.
      responses:
        "200":
          description: List of tenants
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/Tenant"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /v1/publisher/guards/catalog:
    get:
      summary: List platform guard catalog (publisher scope)
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Guard catalog
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/PlatformGuardCatalogEntry" }

  /v1/publisher/payment-sessions:
    get:
      summary: List payment sessions scoped to publisher-owned xapps
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: paymentSessionId
          in: query
          schema: { type: string }
        - name: clientId
          in: query
          schema: { type: string }
        - name: xappId
          in: query
          schema: { type: string }
        - name: toolName
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string }
        - name: issuer
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 500 }
      responses:
        "200":
          description: Payment session list
          content:
            application/json:
              examples:
                publisher_scoped_payment_sessions:
                  summary: Publisher-scoped list with delegated issuer metadata
                  value:
                    items:
                      - payment_session_id: pay_pub_123
                        xapp_id: xapp_pub_1
                        tool_name: purchase
                        amount: "8.00"
                        currency: USD
                        issuer: publisher_delegated
                        status: pending
                        metadata_jsonb:
                          payment_signing:
                            signing_lane: publisher_delegated
                            resolver_source: guard_config_delegated_lane
              schema:
                $ref: "#/components/schemas/PaymentSessionListResponse"

  /v1/publisher/payment-sessions/{id}:
    get:
      summary: Get payment session detail (publisher scope)
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Payment session detail
          content:
            application/json:
              examples:
                publisher_payment_session_detail:
                  value:
                    session:
                      payment_session_id: pay_pub_123
                      issuer: publisher_delegated
                      status: pending
                      amount: "8.00"
                      currency: USD
              schema:
                $ref: "#/components/schemas/PaymentSessionEnvelope"

  /v1/publisher/payment-sessions/{id}/reconcile:
    post:
      summary: Reconcile payment session status (publisher scope)
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PaymentSessionReconcileRequest"
      responses:
        "200":
          description: Reconciled
          content:
            application/json:
              examples:
                publisher_reconcile_result:
                  summary: Publisher reconcile result preserves delegated issuer metadata
                  value:
                    session:
                      payment_session_id: pay_pub_123
                      issuer: publisher_delegated
                      status: completed
                      amount: "8.00"
                      currency: USD
                      metadata_jsonb:
                        payment_signing:
                          signing_lane: publisher_delegated
                          resolver_source: guard_config_delegated_lane
              schema:
                $ref: "#/components/schemas/PaymentSessionEnvelope"

  /v1/publisher/settlement/overview:
    get:
      summary: Get publisher settlement overview
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Settlement overview scoped to the current publisher
          content:
            application/json:
              examples:
                publisher_settlement_overview:
                  summary: Publisher finance snapshot over booked settlement source events
                  value:
                    source_event_count: 1
                    booked_source_event_count: 1
                    gross_by_currency:
                      - currency: EUR
                        amount: "100.00"
                    accounts:
                      - account_id: settle_acc_pub_1
                        owner_kind: publisher
                        owner_ref: pub_1
                        currency: EUR
                        balance: "70.00"
                        entry_count: 1
                    analytics:
                      source_event_progress:
                        open: 1
                        partial: 0
                        settled: 0
                        retained: 0
                      outstanding_aging_by_currency:
                        - currency: EUR
                          current_amount: "70.00"
                          days_8_to_30_amount: "0.00"
                          days_31_plus_amount: "0.00"
                          source_event_count: 1
                      throughput_trend:
                        - window_days: 7
                          current_source_event_count: 1
                          previous_source_event_count: 0
                          current_gross_by_currency:
                            - currency: EUR
                              amount: "100.00"
                          previous_gross_by_currency: []
                        - window_days: 30
                          current_source_event_count: 1
                          previous_source_event_count: 0
                          current_gross_by_currency:
                            - currency: EUR
                              amount: "100.00"
                          previous_gross_by_currency: []
                      closure_rates:
                        - window_days: 7
                          total_source_events: 1
                          closed_source_events: 0
                          closed_rate_percent: "0.00"
                        - window_days: 30
                          total_source_events: 1
                          closed_source_events: 0
                          closed_rate_percent: "0.00"
                      payout_velocity:
                        window_days: 30
                        paid_batch_count: 0
                        average_close_days: null
                        executed_amount_by_currency: []
                    recent_source_events:
                      - id: settle_evt_pub_1
                        occurred_at: "2026-04-09T10:00:00.000Z"
                        currency: EUR
                        gross_amount: "100.00"
                        settlement_basis_amount: "100.00"
                        lane: gateway_managed
                        receiver_of_funds_kind: gateway
                        receiver_of_funds_ref: ""
                        booking_status: booked
                        payment_session_id: pay_pub_123
                        receipt_id: rcpt_pub_123
                        client_id: client_pub_1
                        publisher_id: pub_1
                        xapp_id: xapp_pub_1
                        resolved_split_policy_id: spl_pub_1
                        split_policy_snapshot_ref: platform:default-eur-v1
                        effective_split_policy_source: explicit
                        effective_split_scope_kind: platform
                        effective_split_scope_ref: null
                        effective_split_currency: EUR
                        effective_split_version: "1"
                        effective_split_label: platform · EUR · v1
              schema:
                $ref: "#/components/schemas/SettlementOverviewResponse"

  /v1/publisher/settlement/source-events:
    get:
      summary: List publisher settlement source events
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: bookingStatus
          in: query
          schema: { type: string }
        - name: currency
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 200 }
      responses:
        "200":
          description: Publisher-visible settlement source event list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementSourceEventListResponse"

  /v1/publisher/settlement/source-events/{id}:
    get:
      summary: Get publisher settlement source event detail
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Publisher-visible settlement source event detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementSourceEventDetailEnvelope"

  /v1/publisher/settlement/exports/{dataset}:
    get:
      summary: Export publisher settlement dataset as CSV
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: dataset
          in: path
          required: true
          schema:
            {
              type: string,
              enum:
                [
                  accounts,
                  allocation-ledger,
                  open-obligations,
                  payout-batches,
                  periods,
                  source-events,
                  split-policies,
                ],
            }
      responses:
        "200":
          description: CSV export for the selected publisher settlement dataset
          content:
            text/csv:
              schema:
                type: string

  /v1/publisher/settlement/accounts:
    get:
      summary: List publisher settlement accounts
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: currency
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 300 }
      responses:
        "200":
          description: Publisher settlement accounts
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementAccountListResponse"

  /v1/publisher/settlement/accounts/{id}:
    get:
      summary: Get publisher settlement account detail
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100 }
      responses:
        "200":
          description: Publisher settlement account detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementAccountDetailEnvelope"

  /v1/publisher/settlement/allocation-ledger:
    get:
      summary: List publisher settlement allocation ledger entries
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: paymentSessionId
          in: query
          schema: { type: string }
        - name: sourceEventId
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 300 }
      responses:
        "200":
          description: Publisher settlement allocation ledger list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementAllocationLedgerListResponse"

  /v1/publisher/settlement/split-policies:
    get:
      summary: List publisher settlement split policies
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: currency
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string, enum: [active, disabled, all] }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 500 }
      responses:
        "200":
          description: Publisher settlement split policy list
          content:
            application/json:
              examples:
                publisher_split_policies:
                  value:
                    items:
                      - id: spl_pub_1
                        scope_kind: publisher
                        scope_ref: pub_1
                        target_client_id: tenant_1
                        target_publisher_id: pub_1
                        target_xapp_id: xapp_1
                        target_lane: publisher_delegated
                        currency: EUR
                        basis_kind: gross_charge
                        rounding_policy: largest_remainder_to_receiver
                        status: active
                        effective_from: "2026-04-09T10:00:00.000Z"
                        effective_to: null
                        version: "1"
                        metadata_jsonb: null
                        created_at: "2026-04-09T10:00:00.000Z"
                        updated_at: "2026-04-09T10:00:00.000Z"
                        components:
                          - beneficiary_kind: gateway
                            beneficiary_ref: null
                            share_type: percentage
                            share_value: "10"
                            display_order: 0
                            metadata_jsonb: null
                          - beneficiary_kind: tenant
                            beneficiary_ref: null
                            share_type: percentage
                            share_value: "20"
                            display_order: 1
                            metadata_jsonb: null
                          - beneficiary_kind: publisher
                            beneficiary_ref: null
                            share_type: percentage
                            share_value: "70"
                            display_order: 2
                            metadata_jsonb: null
              schema:
                $ref: "#/components/schemas/SettlementSplitPolicyListResponse"
    post:
      summary: Publisher settlement split policy mutation is platform-managed
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SettlementSplitPolicyInput"
      responses:
        "403":
          description: Publisher finance scope is read-only for settlement split policy authoring

  /v1/publisher/settlement/split-policies/{id}:
    patch:
      summary: Publisher settlement split policy mutation is platform-managed
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SettlementSplitPolicyInput"
      responses:
        "403":
          description: Publisher finance scope is read-only for settlement split policy authoring

  /v1/publisher/settlement/payout-batches:
    get:
      summary: List publisher settlement payout batches
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: status
          in: query
          schema: { type: string, enum: [draft, submitted, paid, rejected, cancelled] }
        - name: currency
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 500 }
      responses:
        "200":
          description: Publisher settlement payout batch list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementPayoutBatchListResponse"
    post:
      summary: Create publisher settlement payout batch draft
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SettlementPayoutBatchCreateInput"
      responses:
        "201":
          description: Created publisher settlement payout batch
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementPayoutBatchDetailEnvelope"

  /v1/publisher/settlement/open-obligations:
    get:
      summary: List publisher open settlement obligations
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: currency
          in: query
          schema: { type: string }
        - name: direction
          in: query
          schema: { type: string, enum: [in, out] }
      responses:
        "200":
          description: Publisher open settlement obligations
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementOpenObligationListResponse"

  /v1/publisher/settlement/payout-obligations:
    get:
      summary: List publisher open payout obligations for draft selection
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: payee_kind
          in: query
          required: true
          schema: { type: string, enum: [gateway, tenant, publisher] }
        - name: payee_ref
          in: query
          schema: { type: string }
        - name: currency
          in: query
          required: true
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 500 }
        - name: source_event_lane
          in: query
          schema:
            {
              type: string,
              enum: [gateway_managed, tenant_delegated, publisher_delegated, owner_managed],
            }
        - name: source_event_xapp_id
          in: query
          schema: { type: string }
        - name: occurred_from
          in: query
          schema: { type: string, format: date-time }
        - name: occurred_to
          in: query
          schema: { type: string, format: date-time }
      responses:
        "200":
          description: Publisher eligible payout obligations
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementEligiblePayoutAllocationListResponse"

  /v1/publisher/settlement/payout-batches/{id}:
    get:
      summary: Get publisher settlement payout batch detail
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Publisher settlement payout batch detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementPayoutBatchDetailEnvelope"

  /v1/publisher/settlement/payout-batches/{id}/proofs:
    post:
      summary: Upload publisher settlement payout proof
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SettlementPayoutProofInput"
      responses:
        "200":
          description: Updated publisher settlement payout batch with proof
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementPayoutBatchDetailEnvelope"

  /v1/publisher/settlement/payout-batches/{id}/proof-review:
    post:
      summary: Review publisher settlement payout proof
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SettlementPayoutProofReviewInput"
      responses:
        "200":
          description: Updated publisher settlement payout batch proof review
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementPayoutBatchDetailEnvelope"

  /v1/publisher/settlement/payout-batches/{id}/status:
    post:
      summary: Update publisher settlement payout batch status
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SettlementPayoutStatusInput"
      responses:
        "200":
          description: Updated publisher settlement payout batch status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementPayoutBatchDetailEnvelope"

  /v1/publisher/settlement/periods:
    get:
      summary: List publisher settlement periods
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: status
          in: query
          schema: { type: string }
        - name: currency
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 500 }
      responses:
        "200":
          description: Publisher settlement period list
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementPayoutPeriodListResponse"

  /v1/publisher/settlement/periods/{id}:
    get:
      summary: Get publisher settlement period detail
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Publisher settlement period detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementPayoutPeriodEnvelope"

  /v1/publisher/settlement/periods/{id}/close:
    post:
      summary: Close publisher settlement period
      x-compatibility-class: A
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SettlementPayoutPeriodCloseInput"
      responses:
        "200":
          description: Closed publisher settlement period
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementPayoutPeriodEnvelope"

  /v1/publisher/installations:
    get:
      summary: List publisher installations
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Installations list

  /v1/publisher/requests:
    get:
      summary: List requests for publisher xapps
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Requests list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        status: { type: string }
                        tool_name: { type: string }
                        guard_summary:
                          $ref: "#/components/schemas/GuardSummary"
                        usage_credit:
                          allOf:
                            - $ref: "#/components/schemas/PaymentUsageCreditSummary"
                          nullable: true
                        ops_attention:
                          allOf:
                            - $ref: "#/components/schemas/RequestOpsAttention"
                          nullable: true
              examples:
                requestsListWithGuardSummary:
                  value:
                    items:
                      - id: req_pub_01
                        status: REJECTED
                        tool_name: submit_wizard_application
                        guard_summary:
                          trigger: "before:tool_run"
                          outcome: blocked
                          slug: publisher-delegated-payment-policy
                          reason: payment_not_settled
                          monetization_state:
                            entitlement_state: unknown
                            balance_state: unknown
                            payment_attempt_state: failed
                            issuer: publisher_delegated
                            authority_lane: publisher_delegated
                            source_ref: publisher-policy/delegated-default
                          action:
                            kind: complete_payment

  /v1/publisher/invoices:
    get:
      summary: List invoices for publisher xapps
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: requestId
          in: query
          schema: { type: string }
        - name: paymentSessionId
          in: query
          schema: { type: string }
        - name: xappId
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string }
        - name: search
          in: query
          schema: { type: string }
        - name: page
          in: query
          schema: { type: integer, minimum: 1 }
        - name: pageSize
          in: query
          schema: { type: integer, minimum: 1, maximum: 100 }
      responses:
        "200":
          description: Publisher invoice list
          content:
            application/json:
              schema:
                type: object
                required: [items]
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/PortalInvoiceSummary"
                  pagination:
                    type: object
                    properties:
                      total: { type: integer }
                      page: { type: integer }
                      pageSize: { type: integer }
                      totalPages: { type: integer }

  /v1/publisher/invoices/{id}/history:
    get:
      summary: Get publisher invoice lifecycle history
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Invoice lifecycle history
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InvoiceHistoryResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

  /v1/publisher/invoices/{id}/void:
    post:
      summary: Void an issued invoice and create a reversal record for a publisher-owned xapp
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                reason: { type: string, nullable: true }
      responses:
        "200":
          description: Invoice voided and reversal created
          content:
            application/json:
              schema:
                type: object
                properties:
                  item:
                    allOf:
                      - $ref: "#/components/schemas/PortalInvoiceSummary"
                    nullable: true
                  reversal:
                    allOf:
                      - $ref: "#/components/schemas/PortalInvoiceSummary"
                    nullable: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

  /v1/publisher/requests/updates:
    get:
      summary: Stream request arrival updates for publisher xapps (SSE)
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      responses:
        "200":
          description: SSE stream
          content:
            text/event-stream:
              schema: { type: string }
        "401":
          $ref: "#/components/responses/Unauthorized"

  /v1/publisher/requests/{id}:
    get:
      summary: Get request details (publisher scope)
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Request details
          content:
            application/json:
              schema:
                type: object
                properties:
                  request:
                    type: object
                    properties:
                      id: { type: string }
                      guard_summary:
                        $ref: "#/components/schemas/GuardSummary"
                      usage_credit:
                        allOf:
                          - $ref: "#/components/schemas/PaymentUsageCreditSummary"
                        nullable: true
                      ops_attention:
                        allOf:
                          - $ref: "#/components/schemas/RequestOpsAttention"
                        nullable: true
                  tool:
                    nullable: true
                  artifacts:
                    type: array
                    items: { type: object, additionalProperties: true }
                  response:
                    nullable: true
              examples:
                requestDetailWithGuardSummary:
                  summary: "Publisher request detail includes guard action summary"
                  value:
                    request:
                      id: req_pub_01
                      payload:
                        companyCui: "RO12345678"
                        requestPurpose: "billing_recovery"
                        subject_profiles:
                          identity_basic:
                            name: "Alex Example"
                            email: "alex@example.com"
                          billing_business:
                            company_name: "Example SRL"
                            company_identification_number: "12345678"
                      guard_summary:
                        trigger: "before:tool_run"
                        outcome: blocked
                        slug: publisher-delegated-payment-policy
                        reason: payment_not_settled
                        monetization_state:
                          entitlement_state: unknown
                          balance_state: unknown
                          payment_attempt_state: failed
                          issuer: publisher_delegated
                          authority_lane: publisher_delegated
                          source_ref: publisher-policy/delegated-default
                        action:
                          kind: complete_payment
                    response: null
                    tool: null
                  artifacts: []

  /v1/publisher/requests/{id}/replay-response-finalized-invoice-hooks:
    post:
      summary: Replay finalized-response invoice hooks for a completed request (publisher scope)
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Finalized-response invoice hooks replayed
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: Replay not allowed for the current request state

  /v1/publisher/requests/{id}/complete-manual:
    post:
      summary: Complete a request manually from publisher console
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [response]
              additionalProperties: false
              properties:
                response: { type: object }
                message: { type: string }
                artifacts:
                  type: array
                  items:
                    type: object
                    additionalProperties: false
                    required: [filename, mimeType, base64]
                    properties:
                      filename: { type: string }
                      mimeType: { type: string }
                      base64: { type: string }
                      kind: { type: string }
                artifact:
                  type: object
                  additionalProperties: false
                  required: [filename, mimeType, base64]
                  properties:
                    filename: { type: string }
                    mimeType: { type: string }
                    base64: { type: string }
                    kind: { type: string }
      responses:
        "200":
          description: Request completed manually

  /v1/publisher/requests/{id}/events:
    get:
      summary: List request events (publisher scope)
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Request events
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/RequestEvent" }

  /v1/publisher/events:
    get:
      summary: List events for publisher xapps
      tags: [Publisher Console]
      description: |
        Lists publisher-visible top-level events and, when `evidenceType` filters are used, may also return
        request-derived evidence rows (for example `REQUEST_EVIDENCE` wrappers for request event evidence).
        Aggregated request-derived evidence row IDs are list-only identifiers and are not guaranteed to resolve via
        `/v1/publisher/events/{id}`.
      security:
        - BearerAuth: []
      parameters:
        - name: type
          in: query
          schema: { type: string }
        - name: xappId
          in: query
          schema: { type: string }
        - name: installationId
          in: query
          schema: { type: string }
        - name: clientId
          in: query
          schema: { type: string }
        - name: trigger
          in: query
          schema:
            type: string
            enum:
              [
                before:installation_create,
                before:widget_load,
                before:session_open,
                before:tool_run,
                before:thread_create,
                after:install,
                after:link_complete,
                before:link_revoke,
                before:uninstall,
                after:tool_complete,
                after:uninstall,
              ]
        - name: outcome
          in: query
          schema: { type: string, enum: [pass, blocked] }
        - name: guardSlug
          in: query
          schema: { type: string }
        - name: evidenceType
          in: query
          schema:
            type: string
            enum:
              [
                GUARD_DECISION,
                DISPATCH_SELECTED,
                CREDENTIAL_BOUND,
                SECRET_RESOLVED,
                PROJECTION_APPLIED,
              ]
        - name: endpointRef
          in: query
          schema: { type: string }
        - name: authLane
          in: query
          schema: { type: string }
        - name: credentialProfile
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 200 }
        - name: cursor
          in: query
          schema: { type: string }
      responses:
        "200":
          description: Events list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/Event" }
              examples:
                topLevelGuardPassSessionOpen:
                  summary: "Top-level guard event (superadmin scope) for before:session_open pass"
                  value:
                    items:
                      - id: evt_admin_top_guard_01
                        type: xapps.guard.evaluated
                        client_id: client_01
                        installation_id: inst_01
                        xapp_id: xapp_01
                        occurred_at: "2026-02-21T12:01:00.000Z"
                        created_at: "2026-02-21T12:01:01.000Z"
                        payload:
                          trigger: "before:session_open"
                          outcome: pass
                          mode: all
                          on_fail: stop_on_fail
                          monetization_verified:
                            - guard_slug: subscription-check
                              kind: subscription
                              monetization_state:
                                entitlement_state: active
                                balance_state: unknown
                                payment_attempt_state: unknown
                                tier: enterprise
                                issuer: tenant
                                authority_lane: owner_managed
                                source_ref: tenant-policy/subscription-default
                          monetization_state:
                            entitlement_state: active
                            balance_state: unknown
                            payment_attempt_state: unknown
                            tier: enterprise
                            issuer: tenant
                            authority_lane: owner_managed
                            source_ref: tenant-policy/subscription-default
                topLevelGuardPassSessionOpenCredits:
                  summary: "Top-level guard event (superadmin scope) for before:session_open credits pass"
                  value:
                    items:
                      - id: evt_admin_top_guard_credits_01
                        type: xapps.guard.evaluated
                        client_id: client_01
                        installation_id: inst_01
                        xapp_id: xapp_01
                        occurred_at: "2026-02-21T12:04:00.000Z"
                        created_at: "2026-02-21T12:04:01.000Z"
                        payload:
                          trigger: "before:session_open"
                          outcome: pass
                          mode: all
                          on_fail: stop_on_fail
                          monetization_verified:
                            - guard_slug: credit-balance-check
                              kind: credits
                              monetization_state:
                                entitlement_state: unknown
                                balance_state: sufficient
                                payment_attempt_state: unknown
                                issuer: tenant
                                authority_lane: owner_managed
                                source_ref: tenant-policy/credits-default
                          monetization_state:
                            entitlement_state: unknown
                            balance_state: sufficient
                            payment_attempt_state: unknown
                            issuer: tenant
                            authority_lane: owner_managed
                            source_ref: tenant-policy/credits-default
                evidenceFilteredDispatchEvents:
                  summary: "Evidence-filtered dispatch events (superadmin scope)"
                  value:
                    items:
                      - id: evt_admin_01
                        type: REQUEST_EVIDENCE
                        client_id: client_01
                        installation_id: inst_01
                        xapp_id: xapp_01
                        occurred_at: "2026-02-21T12:00:00.000Z"
                        created_at: "2026-02-21T12:00:01.000Z"
                        payload:
                          evidence_type: DISPATCH_SELECTED
                          endpoint_ref: prod
                          auth_lane: hmac
                          credential_profile: primary
                          request_id: req_admin_01
                evidenceFilteredGuardEvents:
                  summary: "Evidence-filtered guard events with redacted monetization summary (superadmin scope)"
                  value:
                    items:
                      - id: evt_admin_guard_01
                        type: REQUEST_EVIDENCE
                        client_id: client_01
                        installation_id: inst_01
                        xapp_id: xapp_01
                        occurred_at: "2026-02-21T12:02:00.000Z"
                        created_at: "2026-02-21T12:02:01.000Z"
                        payload:
                          evidence_type: GUARD_EVALUATED
                          request_id: req_admin_02
                          guard:
                            slug: tenant-payment-policy
                            trigger: "before:tool_run"
                            outcome: blocked
                            reason: payment_not_settled
                          monetization_state:
                            payment_attempt_state: failed
                            entitlement_state: unknown
                            balance_state: unknown
                      - id: evt_admin_guard_02
                        type: REQUEST_EVIDENCE
                        client_id: client_01
                        installation_id: inst_01
                        xapp_id: xapp_01
                        occurred_at: "2026-02-21T12:03:00.000Z"
                        created_at: "2026-02-21T12:03:01.000Z"
                        payload:
                          evidence_type: GUARD_EVALUATED
                          request_id: req_admin_03
                          outcome: pass
                          monetization_verified:
                            - guard_slug: credit-balance-check
                              kind: credits
                              monetization_state:
                                entitlement_state: active
                                balance_state: sufficient
                                payment_attempt_state: unknown
                                tier: pro
                                issuer: tenant
                          monetization_state:
                            entitlement_state: active
                            balance_state: sufficient
                            payment_attempt_state: unknown
                            tier: pro
                            issuer: tenant
  /v1/publisher/events/{id}:
    get:
      summary: Get event details and deliveries (publisher scope)
      tags: [Publisher Console]
      description: |
        Returns a top-level event record and its deliveries for a canonical `events.id`.
        IDs returned by evidence-filtered aggregated request-evidence rows from `/v1/publisher/events` may not be
        canonical event IDs and can return `404` here.
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Event details
          content:
            application/json:
              schema:
                type: object
                properties:
                  event: { $ref: "#/components/schemas/Event" }
                  deliveries:
                    type: array
                    items: { $ref: "#/components/schemas/EventDelivery" }

  /v1/publisher/event-deliveries:
    get:
      summary: List event deliveries for publisher xapps
      tags: [Publisher Console]
      description: |
        Returns webhook delivery records for xapps owned by the authenticated publisher.

        Auth accepted:
        - `Authorization: Bearer <publisher_session_token>`
        - `Authorization: Bearer <publisher_callback_token>`

        Callback-token boundary:
        - if the callback token includes `clientId`, listing is constrained to that client.
        - requesting a different `clientId` returns `403`.
      security:
        - BearerAuth: []
      parameters:
        - name: eventId
          in: query
          schema: { type: string }
        - name: installationId
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string, enum: [pending, completed, failed, dead_letter] }
        - name: xappId
          in: query
          schema: { type: string }
        - name: clientId
          in: query
          schema: { type: string }
      responses:
        "200":
          description: Deliveries list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/EventDelivery" }
        "403":
          description: Callback token not allowed for requested client scope

  /v1/publisher/event-deliveries/{id}/redeliver:
    post:
      summary: Manually trigger redelivery of an event
      tags: [Publisher Console]
      description: |
        Requeues a single delivery in the publisher scope and resets retry state.

        Auth accepted:
        - `Authorization: Bearer <publisher_session_token>`
        - `Authorization: Bearer <publisher_callback_token>`

        Callback-token boundary:
        - if the callback token includes `clientId`, only deliveries in that client are redeliverable.
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Delivery requeued
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
        "401":
          description: Missing/invalid auth token
        "403":
          description: Callback token not allowed for requested client scope
        "404":
          description: Delivery not found in publisher scope

  /v1/publisher/event-deliveries/redeliver-dlq:
    post:
      summary: Bulk redeliver dead-letter webhook deliveries
      tags: [Publisher Console]
      description: |
        Requeues dead-letter deliveries for a publisher scope, optionally filtered by `xappId`/`clientId`.

        Auth accepted:
        - `Authorization: Bearer <publisher_session_token>`
        - `Authorization: Bearer <publisher_callback_token>`

        Callback-token boundary:
        - if the callback token includes `clientId`, replay is constrained to that client.
        - specifying another `clientId` returns `403`.
      security:
        - BearerAuth: []
      parameters:
        - name: xappId
          in: query
          schema: { type: string }
        - name: clientId
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 500 }
      responses:
        "200":
          description: Dead-letter deliveries requeued
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  queued: { type: integer }
                  scanned: { type: integer }
                  duplicate: { type: boolean }
        "401":
          description: Missing/invalid auth token
        "403":
          description: Callback token not allowed for requested client scope

  /v1/publisher/webhooks/events:
    get:
      summary: Discover supported webhook event types
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Event types

  /v1/publisher/webhooks:
    get:
      summary: List managed publisher webhook targets
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: eventType
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string, enum: [active, disabled] }
      responses:
        "200":
          description: Targets list
    post:
      summary: Create managed publisher webhook target
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PublisherWebhookTargetInput" }
      responses:
        "201":
          description: Created

  /v1/publisher/webhooks/{id}:
    patch:
      summary: Update managed publisher webhook target
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PublisherWebhookTargetInput" }
      responses:
        "200":
          description: Updated
    delete:
      summary: Delete managed publisher webhook target
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Deleted

  /v1/publisher/xapps/{id}/test-event:
    post:
      summary: Trigger a test event for all installations
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  installationCount: { type: number }

  /v1/publisher/webhooks/test:
    get:
      summary: List received mock webhooks
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Webhooks list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/PublisherMockWebhook" }

    delete:
      summary: Clear received mock webhooks
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      responses:
        "200":
          description: OK

  /v1/publisher/webhooks/test/{xappId}:
    post:
      summary: Public endpoint to receive mock webhooks
      tags: [Publisher Console]
      description: |
        This endpoint is used by the gateway to deliver webhooks when testing.
        It verifies signatures and stores the payload for the publisher to view in their console.
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: x-xapps-kid
          in: header
          required: true
          schema: { type: string }
        - name: x-xapps-ts
          in: header
          required: true
          schema: { type: string }
        - name: x-xapps-signature
          in: header
          required: true
          schema: { type: string }
        - name: x-xapps-nonce
          in: header
          required: false
          schema: { type: string }
        - name: x-xapps-source
          in: header
          required: false
          schema: { type: string, enum: [dispatch, event_delivery] }
      responses:
        "200":
          description: OK
        "400":
          description: Invalid request (missing xapp endpoint/credential prerequisites)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "401":
          description: Signature verification failed
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "404":
          description: Xapp not found
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }
        "503":
          description: Secret resolution failed (fail-closed)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ErrorResponse" }

  /v1/publisher/egress-targets:
    get:
      summary: List publisher egress targets
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Targets list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/PublisherEgressTarget" }
    post:
      summary: Create publisher egress target
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          schema: { type: string }
      responses:
        "201":
          description: Created

  /v1/publisher/egress-targets/{id}:
    patch:
      summary: Update publisher egress target
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Updated
    delete:
      summary: Delete publisher egress target
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Deleted

  /v1/publisher/egress-audit-logs:
    get:
      summary: List publisher egress audit logs
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: targetId
          in: query
          schema: { type: string }
        - name: toolName
          in: query
          schema: { type: string }
        - name: statusFamily
          in: query
          schema: { type: string, enum: ["2xx", "3xx", "4xx", "5xx"] }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 500 }
      responses:
        "200":
          description: Audit list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/PublisherEgressAuditLog" }

  /v1/proxy/publisher/egress/{targetId}/{wildcard}:
    get:
      summary: Proxy GET request to a publisher-managed egress target
      tags: [Proxy]
      security:
        - BearerAuth: []
      parameters:
        - name: targetId
          in: path
          required: true
          schema: { type: string }
        - name: wildcard
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Proxied response
    post:
      summary: Proxy POST request to a publisher-managed egress target
      tags: [Proxy]
      security:
        - BearerAuth: []
      parameters:
        - name: targetId
          in: path
          required: true
          schema: { type: string }
        - name: wildcard
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Proxied response

    put:
      summary: Proxy PUT request to a publisher-managed egress target
      tags: [Proxy]
      security:
        - BearerAuth: []
      parameters:
        - name: targetId
          in: path
          required: true
          schema: { type: string }
        - name: wildcard
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Proxied response

    patch:
      summary: Proxy PATCH request to a publisher-managed egress target
      tags: [Proxy]
      security:
        - BearerAuth: []
      parameters:
        - name: targetId
          in: path
          required: true
          schema: { type: string }
        - name: wildcard
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Proxied response

    delete:
      summary: Proxy DELETE request to a publisher-managed egress target
      tags: [Proxy]
      security:
        - BearerAuth: []
      parameters:
        - name: targetId
          in: path
          required: true
          schema: { type: string }
        - name: wildcard
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Proxied response

  /v1/publishers/{publisherId}/webhooks:
    get:
      summary: List managed webhook targets (publisher-scoped alias)
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: publisherId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Targets list
    post:
      summary: Create managed webhook target (publisher-scoped alias)
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: publisherId
          in: path
          required: true
          schema: { type: string }
        - name: Idempotency-Key
          in: header
          required: false
          schema: { type: string }
      responses:
        "201":
          description: Created

  /v1/publishers/{publisherId}/webhooks/{id}:
    patch:
      summary: Update managed webhook target (publisher-scoped alias)
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: publisherId
          in: path
          required: true
          schema: { type: string }
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Updated
    delete:
      summary: Delete managed webhook target (publisher-scoped alias)
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: publisherId
          in: path
          required: true
          schema: { type: string }
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Deleted

  /v1/publishers/{publisherId}/webhooks/events:
    get:
      summary: Discover event types (publisher-scoped alias)
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: publisherId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Event types

  /v1/publisher/endpoints/{endpointId}/credentials:
    get:
      summary: List credentials for an endpoint
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: endpointId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Credentials list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/EndpointCredential" }

    post:
      summary: Create endpoint credential
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: endpointId
          in: path
          required: true
          schema: { type: string }
      description: |
        Creates an auth configuration for an endpoint. For HMAC connector auth, store routing paths in `config`.

        **HMAC signature headers used by the gateway worker when calling publisher endpoints**:
        - `x-xapps-kid`: active key id
        - `x-xapps-ts`: unix timestamp seconds
        - `x-xapps-signature`: base64url(signature(canonical, credential key algorithm))
        - `x-xapps-source`: signing context (`dispatch` or `event_delivery`)

        Canonical string baseline: `METHOD\nPATH_WITH_QUERY\nTIMESTAMP\nBODY_SHA256_HEX`.
        In strict source mode, source and nonce are included in the signed message.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [authType]
              additionalProperties: false
              properties:
                authType:
                  type: string
                  enum:
                    [
                      hmac,
                      mtls,
                      oauth2_cc,
                      api_key_header,
                      none,
                      oauth2,
                      bearer,
                      header,
                      query,
                      api-key,
                      apikey,
                    ]
                  example: hmac
                config:
                  $ref: "#/components/schemas/EndpointCredentialConfig"
                initialKey:
                  type: object
                  additionalProperties: false
                  properties:
                    secret: { type: string, description: "HMAC secret (will be stored encrypted)" }
                    secretRef:
                      type: string
                      description: "Secret reference (supported: env:NAME; vault://... when VAULT_ADDR + VAULT_TOKEN are configured; awssm://... when AWS_REGION + AWS credentials are configured)"
                    status: { type: string, example: active }
                    algorithm: { type: string, enum: [hmac-sha256, hmac-sha512, ed25519] }
                    notBefore: { type: string, format: date-time }
                    notAfter: { type: string, format: date-time }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  credential: { $ref: "#/components/schemas/EndpointCredential" }

  /v1/publisher/endpoints/{endpointId}:
    patch:
      summary: Update endpoint metadata/configuration
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: endpointId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                env: { type: string }
                baseUrl: { type: string }
                timeoutMs: { type: number }
                healthCheck:
                  type: object
                  additionalProperties: false
                  properties:
                    path: { type: string }
                    intervalS: { type: number }
                retryPolicy:
                  type: object
                  additionalProperties: false
                  properties:
                    maxRetries: { type: number }
                    backoff: { type: string, enum: [none, linear, exponential] }
                region: { type: string }
                labels:
                  type: object
                  additionalProperties: { type: string }
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { $ref: "#/components/schemas/XappEndpoint" }

  /v1/publisher/credentials/{credentialId}:
    patch:
      summary: Update endpoint credential auth/config
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: credentialId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                authType:
                  type: string
                  enum:
                    [
                      hmac,
                      mtls,
                      oauth2_cc,
                      api_key_header,
                      none,
                      oauth2,
                      bearer,
                      header,
                      query,
                      api-key,
                      apikey,
                    ]
                config:
                  $ref: "#/components/schemas/EndpointCredentialConfig"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  credential: { $ref: "#/components/schemas/EndpointCredential" }

  /v1/publisher/credentials/{credentialId}/keys:
    get:
      summary: List credential keys
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: credentialId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Keys list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/CredentialKey" }

    post:
      summary: Create credential key
      tags: [Publisher Console]
      security:
        - BearerAuth: []
      parameters:
        - name: credentialId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                secret: { type: string, description: "HMAC secret (will be stored encrypted)" }
                secretRef:
                  type: string
                  description: "Secret reference (supported: env:NAME; vault://... when VAULT_ADDR + VAULT_TOKEN are configured; awssm://... when AWS_REGION + AWS credentials are configured)"
                status: { type: string, example: active }
                algorithm: { type: string, enum: [hmac-sha256, hmac-sha512, ed25519] }
                notBefore: { type: string, format: date-time }
                notAfter: { type: string, format: date-time }
                makeActive: { type: boolean }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  key: { $ref: "#/components/schemas/CredentialKey" }

  /v1/signing/test-vectors:
    get:
      summary: Get signing test vectors for SDK integration testing
      tags: [Security]
      description: |
        Returns known-good JCS canonicalization and SHA-256 test vectors.
        Used by publisher SDKs to verify their signing implementation.
      responses:
        "200":
          description: Test vectors
          content:
            application/json:
              schema:
                type: object
                properties:
                  vectors:
                    type: array
                    items:
                      type: object
                      properties:
                        name: { type: string }
                        input: { type: object }
                        jcs: { type: string }
                        hash: { type: string }

  /v1/superadmin/auth/logout:
    post:
      summary: Superadmin logout
      tags: [Superadmin]
      description: |
        Revokes the active session token (when present via Bearer or `xapps_superadmin_token` cookie)
        and clears the HttpOnly superadmin cookie.
      responses:
        "200":
          description: Logged out

  /v1/superadmin/clients:
    get:
      summary: List clients
      tags: [Superadmin]
      description: |
        Supports superadmin and tenant bearer sessions.
        Tenant role is scoped to its own client and receives at most one item.
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Clients list

    post:
      summary: Create client
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, slug]
              additionalProperties: false
              properties:
                name: { type: string }
                slug: { type: string }
                status: { type: string }
                auth_method: { type: string }
                oidc_issuer_url: { type: string }
                oidc_client_id: { type: string }
                oidc_discovery_url: { type: string }
                logo_url: { type: string, format: uri }
                primary_color: { type: string }
                dashboard_url: { type: string, format: uri }
                details_jsonb: { type: object, additionalProperties: true }
                payment_ui:
                  $ref: "#/components/schemas/HostedPaymentUiContract"
                payment_provider_credentials_refs:
                  $ref: "#/components/schemas/ProviderCredentialRefsContract"
                payment_provider_secret_refs:
                  $ref: "#/components/schemas/ProviderCredentialRefsContract"
                notification_ui:
                  $ref: "#/components/schemas/NotificationUiContract"
                notification_provider_credentials_refs:
                  $ref: "#/components/schemas/ProviderCredentialRefsContract"
                notification_provider_secret_refs:
                  $ref: "#/components/schemas/ProviderCredentialRefsContract"
                notification_template_overrides:
                  $ref: "#/components/schemas/NotificationTemplateOverridesContract"
                invoice_ui:
                  $ref: "#/components/schemas/InvoiceUiContract"
                invoice_provider_credentials_refs:
                  $ref: "#/components/schemas/InvoiceProviderRefsContract"
                invoice_provider_secret_refs:
                  $ref: "#/components/schemas/InvoiceProviderRefsContract"
                invoice_template_overrides:
                  $ref: "#/components/schemas/InvoiceTemplateOverridesContract"
      responses:
        "201":
          description: Created

  /v1/superadmin/tenant-scopes:
    get:
      summary: List tenant scopes for current tenant-mode admin session
      tags: [Superadmin]
      description: |
        Returns tenant scopes available to the current authenticated user when operating
        in tenant mode (`x-xapps-admin-mode: tenant`).
        Optional `x-xapps-tenant-scope-id` selects a specific authorized tenant scope.
      security:
        - BearerAuth: []
      parameters:
        - name: x-xapps-admin-mode
          in: header
          required: false
          schema: { type: string, enum: [tenant, superadmin] }
        - name: x-xapps-tenant-scope-id
          in: header
          required: false
          schema: { type: string }
      responses:
        "200":
          description: Tenant scopes for current user/session
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        name: { type: string }
                        slug: { type: string }
                        status: { type: string }
                  selected_client_id: { type: string, nullable: true }
        "401":
          description: Admin auth required or invalid tenant scope selection

  /v1/superadmin/system-users:
    get:
      summary: List system users and effective roles
      tags: [Superadmin]
      description: |
        Superadmin-only endpoint used for RBAC management.
        Returns each user's legacy primary role and effective global active roles.
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: search
          in: query
          schema: { type: string }
        - name: role
          in: query
          schema: { type: string, enum: [superadmin, tenant, publisher] }
        - name: status
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 1000 }
      responses:
        "200":
          description: System users list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        email: { type: string }
                        status: { type: string }
                        legacy_role: { type: string, nullable: true }
                        roles:
                          type: array
                          items: { type: string }
        "400":
          description: Invalid role filter

  /v1/superadmin/system-users/{id}/roles:
    patch:
      summary: Update a system user's global roles
      tags: [Superadmin]
      description: |
        Superadmin-only endpoint for assigning global roles.
        Also updates the legacy primary role field using role precedence.
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [roles]
              additionalProperties: false
              properties:
                roles:
                  type: array
                  items: { type: string, enum: [superadmin, tenant, publisher] }
      responses:
        "200":
          description: Updated user roles
          content:
            application/json:
              schema:
                type: object
                properties:
                  user:
                    type: object
                    properties:
                      id: { type: string }
                      email: { type: string }
                      status: { type: string }
                      legacy_role: { type: string, nullable: true }
                      roles:
                        type: array
                        items: { type: string }
        "400":
          description: Invalid role payload
        "404":
          description: User not found

  /v1/superadmin/clients/{id}:
    patch:
      summary: Update client
      tags: [Superadmin]
      description: |
        Supports superadmin and tenant bearer sessions.
        Tenant role can update only its own client and cannot change restricted fields.
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                name: { type: string }
                slug: { type: string }
                status: { type: string }
                auth_method: { type: string }
                oidc_issuer_url: { type: string }
                oidc_client_id: { type: string }
                oidc_discovery_url: { type: string }
                logo_url: { type: string, format: uri }
                primary_color: { type: string }
                dashboard_url: { type: string, format: uri }
                details_jsonb: { type: object, additionalProperties: true }
                payment_ui:
                  $ref: "#/components/schemas/HostedPaymentUiContract"
                payment_provider_credentials_refs:
                  $ref: "#/components/schemas/ProviderCredentialRefsContract"
                payment_provider_secret_refs:
                  $ref: "#/components/schemas/ProviderCredentialRefsContract"
                notification_ui:
                  $ref: "#/components/schemas/NotificationUiContract"
                notification_provider_credentials_refs:
                  $ref: "#/components/schemas/ProviderCredentialRefsContract"
                notification_provider_secret_refs:
                  $ref: "#/components/schemas/ProviderCredentialRefsContract"
                notification_template_overrides:
                  $ref: "#/components/schemas/NotificationTemplateOverridesContract"
                invoice_ui:
                  $ref: "#/components/schemas/InvoiceUiContract"
                invoice_provider_credentials_refs:
                  $ref: "#/components/schemas/InvoiceProviderRefsContract"
                invoice_provider_secret_refs:
                  $ref: "#/components/schemas/InvoiceProviderRefsContract"
                invoice_template_overrides:
                  $ref: "#/components/schemas/InvoiceTemplateOverridesContract"
      responses:
        "200":
          description: Updated

  /v1/superadmin/clients/{id}/check-jwks:
    post:
      summary: Check JWKS configuration for a client
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: JWKS check result

  /v1/superadmin/clients/{id}/api-keys:
    get:
      summary: List API keys for a client
      tags: [Superadmin]
      description: |
        Supports superadmin and tenant bearer sessions.
        Tenant role can access keys only for its own client.
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: API keys list

    post:
      summary: Issue client API key
      tags: [Superadmin]
      description: |
        Supports superadmin and tenant bearer sessions.
        Tenant role can issue keys only for its own client.
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "201":
          description: Created

  /v1/superadmin/api-keys/{apiKeyId}/revoke:
    post:
      summary: Revoke a client API key
      tags: [Superadmin]
      description: |
        Supports superadmin and tenant bearer sessions.
        Tenant role can revoke only keys owned by its own client.
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: apiKeyId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Revoked

  /v1/superadmin/publishers:
    get:
      summary: List publishers
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Publishers list

    post:
      summary: Create publisher
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name, slug]
              additionalProperties: false
              properties:
                name: { type: string }
                slug: { type: string }
                type: { type: string }
                client_id: { type: string, nullable: true }
                status: { type: string }
                logo_url: { type: string, format: uri }
                details_jsonb: { type: object, additionalProperties: true }
                payment_provider_credentials_refs:
                  $ref: "#/components/schemas/ProviderCredentialRefsContract"
                payment_provider_secret_refs:
                  $ref: "#/components/schemas/ProviderCredentialRefsContract"
                notification_ui:
                  $ref: "#/components/schemas/NotificationUiContract"
                notification_provider_credentials_refs:
                  $ref: "#/components/schemas/ProviderCredentialRefsContract"
                notification_provider_secret_refs:
                  $ref: "#/components/schemas/ProviderCredentialRefsContract"
                notification_template_overrides:
                  $ref: "#/components/schemas/NotificationTemplateOverridesContract"
                invoice_ui:
                  $ref: "#/components/schemas/InvoiceUiContract"
                invoice_provider_credentials_refs:
                  $ref: "#/components/schemas/InvoiceProviderRefsContract"
                invoice_provider_secret_refs:
                  $ref: "#/components/schemas/InvoiceProviderRefsContract"
                invoice_template_overrides:
                  $ref: "#/components/schemas/InvoiceTemplateOverridesContract"
      responses:
        "201":
          description: Created

  /v1/superadmin/guards/catalog:
    get:
      summary: List platform guard catalog
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Guard catalog
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/PlatformGuardCatalogEntry" }

  /v1/superadmin/payment-sessions:
    get:
      summary: List gateway payment sessions (superadmin scope)
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: paymentSessionId
          in: query
          schema: { type: string }
        - name: clientId
          in: query
          schema: { type: string }
        - name: xappId
          in: query
          schema: { type: string }
        - name: toolName
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string }
        - name: issuer
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 500 }
      responses:
        "200":
          description: Payment session list
          content:
            application/json:
              examples:
                superadmin_payment_sessions:
                  summary: Superadmin list includes mixed issuer lanes
                  value:
                    items:
                      - payment_session_id: pay_sa_123
                        xapp_id: xapp_ops_1
                        tool_name: submit
                        amount: "10.00"
                        currency: USD
                        issuer: gateway
                        status: cancelled
                      - payment_session_id: pay_sa_124
                        xapp_id: xapp_ops_2
                        tool_name: submit
                        amount: "12.00"
                        currency: USD
                        issuer: tenant_delegated
                        status: pending
                        metadata_jsonb:
                          payment_signing:
                            signing_lane: tenant_delegated
                            resolver_source: guard_config_delegated_lane
              schema:
                $ref: "#/components/schemas/PaymentSessionListResponse"

  /v1/superadmin/payment-sessions/{id}:
    get:
      summary: Get payment session detail (superadmin scope)
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Payment session detail
          content:
            application/json:
              examples:
                superadmin_payment_session_detail:
                  value:
                    session:
                      payment_session_id: pay_sa_123
                      issuer: gateway
                      status: cancelled
                      amount: "10.00"
                      currency: USD
              schema:
                $ref: "#/components/schemas/PaymentSessionEnvelope"

  /v1/superadmin/payment-sessions/{id}/reconcile:
    post:
      summary: Reconcile payment session status (superadmin scope)
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PaymentSessionReconcileRequest"
      responses:
        "200":
          description: Reconciled
          content:
            application/json:
              examples:
                superadmin_reconcile_result:
                  summary: Superadmin reconcile result includes delegated signing metadata when present
                  value:
                    session:
                      payment_session_id: pay_sa_124
                      issuer: tenant_delegated
                      status: completed
                      amount: "12.00"
                      currency: USD
                      metadata_jsonb:
                        payment_signing:
                          signing_lane: tenant_delegated
                          resolver_source: guard_config_delegated_lane
              schema:
                $ref: "#/components/schemas/PaymentSessionEnvelope"

  /v1/superadmin/settlement/overview:
    get:
      summary: Get settlement overview for gateway or tenant finance scope
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Settlement overview for the current superadmin or tenant-admin finance scope
          content:
            application/json:
              examples:
                superadmin_settlement_overview:
                  summary: Gateway-scoped finance snapshot over booked settlement source events
                  value:
                    source_event_count: 2
                    booked_source_event_count: 2
                    gross_by_currency:
                      - currency: EUR
                        amount: "150.00"
                    accounts:
                      - account_id: settle_acc_gateway_eur
                        owner_kind: gateway
                        owner_ref: ""
                        currency: EUR
                        balance: "150.00"
                        entry_count: 2
                    analytics:
                      source_event_progress:
                        open: 2
                        partial: 0
                        settled: 0
                        retained: 0
                      outstanding_aging_by_currency:
                        - currency: EUR
                          current_amount: "150.00"
                          days_8_to_30_amount: "0.00"
                          days_31_plus_amount: "0.00"
                          source_event_count: 2
                      throughput_trend:
                        - window_days: 7
                          current_source_event_count: 2
                          previous_source_event_count: 0
                          current_gross_by_currency:
                            - currency: EUR
                              amount: "150.00"
                          previous_gross_by_currency: []
                        - window_days: 30
                          current_source_event_count: 2
                          previous_source_event_count: 0
                          current_gross_by_currency:
                            - currency: EUR
                              amount: "150.00"
                          previous_gross_by_currency: []
                      closure_rates:
                        - window_days: 7
                          total_source_events: 2
                          closed_source_events: 0
                          closed_rate_percent: "0.00"
                        - window_days: 30
                          total_source_events: 2
                          closed_source_events: 0
                          closed_rate_percent: "0.00"
                      payout_velocity:
                        window_days: 30
                        paid_batch_count: 0
                        average_close_days: null
                        executed_amount_by_currency: []
                    recent_source_events:
                      - id: settle_evt_sa_1
                        occurred_at: "2026-04-09T10:00:00.000Z"
                        currency: EUR
                        gross_amount: "100.00"
                        settlement_basis_amount: "100.00"
                        lane: gateway_managed
                        receiver_of_funds_kind: gateway
                        receiver_of_funds_ref: ""
                        booking_status: booked
                        payment_session_id: pay_sa_123
                        receipt_id: rcpt_sa_123
                        client_id: client_ops_1
                        publisher_id: pub_ops_1
                        xapp_id: xapp_ops_1
                        resolved_split_policy_id: spl_platform_1
                        split_policy_snapshot_ref: platform:default-eur-v1
                        effective_split_policy_source: explicit
                        effective_split_scope_kind: platform
                        effective_split_scope_ref: null
                        effective_split_currency: EUR
                        effective_split_version: "1"
                        effective_split_label: platform · EUR · v1
              schema:
                $ref: "#/components/schemas/SettlementOverviewResponse"

  /v1/superadmin/settlement/source-events:
    get:
      summary: List settlement source events for admin finance scope
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: clientId
          in: query
          schema: { type: string }
        - name: publisherId
          in: query
          schema: { type: string }
        - name: xappId
          in: query
          schema: { type: string }
        - name: bookingStatus
          in: query
          schema: { type: string }
        - name: currency
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 200 }
      responses:
        "200":
          description: Settlement source events for the current admin finance scope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementSourceEventListResponse"

  /v1/superadmin/settlement/source-events/{id}:
    get:
      summary: Get settlement source event detail for admin finance scope
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Settlement source event detail for the current admin finance scope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementSourceEventDetailEnvelope"

  /v1/superadmin/settlement/exports/{dataset}:
    get:
      summary: Export settlement dataset as CSV for admin finance scope
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: dataset
          in: path
          required: true
          schema:
            {
              type: string,
              enum:
                [
                  accounts,
                  allocation-ledger,
                  open-obligations,
                  payout-batches,
                  periods,
                  source-events,
                  split-policies,
                ],
            }
      responses:
        "200":
          description: CSV export for the selected settlement dataset
          content:
            text/csv:
              schema:
                type: string

  /v1/superadmin/settlement/accounts:
    get:
      summary: List settlement accounts for admin finance scope
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: ownerKind
          in: query
          schema: { type: string, enum: [gateway, tenant, publisher] }
        - name: ownerRef
          in: query
          schema: { type: string }
        - name: currency
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 300 }
      responses:
        "200":
          description: Settlement accounts for the current admin finance scope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementAccountListResponse"

  /v1/superadmin/settlement/accounts/{id}:
    get:
      summary: Get settlement account detail for admin finance scope
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 100 }
      responses:
        "200":
          description: Settlement account detail for the current admin finance scope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementAccountDetailEnvelope"

  /v1/superadmin/settlement/allocation-ledger:
    get:
      summary: List settlement allocation ledger entries for admin finance scope
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: ownerKind
          in: query
          schema: { type: string, enum: [gateway, tenant, publisher] }
        - name: ownerRef
          in: query
          schema: { type: string }
        - name: clientId
          in: query
          schema: { type: string }
        - name: publisherId
          in: query
          schema: { type: string }
        - name: xappId
          in: query
          schema: { type: string }
        - name: paymentSessionId
          in: query
          schema: { type: string }
        - name: sourceEventId
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 300 }
      responses:
        "200":
          description: Settlement allocation ledger for the current admin finance scope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementAllocationLedgerListResponse"

  /v1/superadmin/settlement/split-policies:
    get:
      summary: List settlement split policies for admin finance scope
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: scopeKind
          in: query
          schema: { type: string, enum: [platform, tenant, publisher, xapp] }
        - name: scopeRef
          in: query
          schema: { type: string }
        - name: currency
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string, enum: [active, disabled, all] }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 500 }
      responses:
        "200":
          description: Settlement split policy list for the current admin finance scope
          content:
            application/json:
              examples:
                superadmin_split_policies:
                  value:
                    items:
                      - id: spl_platform_1
                        scope_kind: platform
                        scope_ref: null
                        target_client_id: tenant_1
                        target_publisher_id: pub_1
                        target_xapp_id: xapp_1
                        target_lane: publisher_delegated
                        currency: EUR
                        basis_kind: gross_charge
                        rounding_policy: largest_remainder_to_receiver
                        status: active
                        effective_from: "2026-04-09T10:00:00.000Z"
                        effective_to: null
                        version: "1"
                        metadata_jsonb: null
                        created_at: "2026-04-09T10:00:00.000Z"
                        updated_at: "2026-04-09T10:00:00.000Z"
                        components:
                          - beneficiary_kind: gateway
                            beneficiary_ref: null
                            share_type: percentage
                            share_value: "10"
                            display_order: 0
                            metadata_jsonb: null
                          - beneficiary_kind: tenant
                            beneficiary_ref: null
                            share_type: percentage
                            share_value: "20"
                            display_order: 1
                            metadata_jsonb: null
                          - beneficiary_kind: publisher
                            beneficiary_ref: null
                            share_type: percentage
                            share_value: "70"
                            display_order: 2
                            metadata_jsonb: null
              schema:
                $ref: "#/components/schemas/SettlementSplitPolicyListResponse"
    post:
      summary: Create settlement split policy for admin finance scope
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SettlementSplitPolicyInput"
      responses:
        "201":
          description: Created settlement split policy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementSplitPolicyEnvelope"

  /v1/superadmin/settlement/split-policies/{id}:
    patch:
      summary: Update settlement split policy for admin finance scope
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SettlementSplitPolicyInput"
      responses:
        "200":
          description: Updated settlement split policy
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementSplitPolicyEnvelope"

  /v1/superadmin/settlement/payout-batches:
    get:
      summary: List settlement payout batches for admin finance scope
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: ownerKind
          in: query
          schema: { type: string, enum: [gateway, tenant, publisher] }
        - name: ownerRef
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string, enum: [draft, submitted, paid, rejected, cancelled] }
        - name: currency
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 500 }
      responses:
        "200":
          description: Settlement payout batch list for admin finance scope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementPayoutBatchListResponse"
    post:
      summary: Create settlement payout batch draft for admin finance scope
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SettlementPayoutBatchCreateInput"
      responses:
        "201":
          description: Created settlement payout batch
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementPayoutBatchDetailEnvelope"

  /v1/superadmin/settlement/open-obligations:
    get:
      summary: List open settlement obligations for admin finance scope
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: ownerKind
          in: query
          schema: { type: string, enum: [gateway, tenant, publisher] }
        - name: ownerRef
          in: query
          schema: { type: string }
        - name: currency
          in: query
          schema: { type: string }
        - name: direction
          in: query
          schema: { type: string, enum: [in, out] }
      responses:
        "200":
          description: Open settlement obligations for admin finance scope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementOpenObligationListResponse"

  /v1/superadmin/settlement/payout-obligations:
    get:
      summary: List eligible payout obligations for admin finance draft selection
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: payer_kind
          in: query
          schema: { type: string, enum: [gateway, tenant, publisher] }
        - name: payer_ref
          in: query
          schema: { type: string }
        - name: payee_kind
          in: query
          required: true
          schema: { type: string, enum: [gateway, tenant, publisher] }
        - name: payee_ref
          in: query
          schema: { type: string }
        - name: currency
          in: query
          required: true
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 500 }
        - name: source_event_lane
          in: query
          schema:
            {
              type: string,
              enum: [gateway_managed, tenant_delegated, publisher_delegated, owner_managed],
            }
        - name: source_event_xapp_id
          in: query
          schema: { type: string }
        - name: occurred_from
          in: query
          schema: { type: string, format: date-time }
        - name: occurred_to
          in: query
          schema: { type: string, format: date-time }
      responses:
        "200":
          description: Eligible payout obligations for admin finance scope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementEligiblePayoutAllocationListResponse"

  /v1/superadmin/settlement/payout-batches/{id}:
    get:
      summary: Get settlement payout batch detail for admin finance scope
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Settlement payout batch detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementPayoutBatchDetailEnvelope"

  /v1/superadmin/settlement/payout-batches/{id}/proofs:
    post:
      summary: Upload settlement payout proof for admin finance scope
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SettlementPayoutProofInput"
      responses:
        "200":
          description: Updated settlement payout batch with proof
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementPayoutBatchDetailEnvelope"

  /v1/superadmin/settlement/payout-batches/{id}/proof-review:
    post:
      summary: Review settlement payout proof for admin finance scope
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SettlementPayoutProofReviewInput"
      responses:
        "200":
          description: Updated settlement payout batch proof review
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementPayoutBatchDetailEnvelope"

  /v1/superadmin/settlement/payout-batches/{id}/status:
    post:
      summary: Update settlement payout batch status for admin finance scope
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SettlementPayoutStatusInput"
      responses:
        "200":
          description: Updated settlement payout batch status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementPayoutBatchDetailEnvelope"

  /v1/superadmin/settlement/periods:
    get:
      summary: List settlement periods for admin finance scope
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: ownerKind
          in: query
          schema: { type: string, enum: [gateway, tenant, publisher] }
        - name: ownerRef
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string }
        - name: currency
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 500 }
      responses:
        "200":
          description: Settlement period list for admin finance scope
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementPayoutPeriodListResponse"

  /v1/superadmin/settlement/periods/{id}:
    get:
      summary: Get settlement period detail for admin finance scope
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Settlement period detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementPayoutPeriodEnvelope"

  /v1/superadmin/settlement/periods/{id}/close:
    post:
      summary: Close settlement period for admin finance scope
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SettlementPayoutPeriodCloseInput"
      responses:
        "200":
          description: Closed settlement period
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SettlementPayoutPeriodEnvelope"

  /v1/superadmin/settlement/maintenance/reconcile-missing:
    post:
      summary: Run settlement catch-up over completed payment sessions
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                limit: { type: integer, minimum: 1, maximum: 1000 }
      responses:
        "200":
          description: Settlement catch-up summary
          content:
            application/json:
              schema:
                type: object
                properties:
                  scanned: { type: integer }
                  reconciled: { type: integer }
                  skipped: { type: integer }
                  failed: { type: integer }
                  failures:
                    type: array
                    items:
                      type: object
                      properties:
                        payment_session_id: { type: string }
                        message: { type: string }

  /v1/superadmin/settlement/maintenance/repair-implicit-splits:
    post:
      summary: Repair implicit settlement source event split bookings
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                limit: { type: integer, minimum: 1, maximum: 1000 }
      responses:
        "200":
          description: Implicit split repair summary
          content:
            application/json:
              schema:
                type: object
                properties:
                  scanned: { type: integer }
                  repaired: { type: integer }
                  skipped: { type: integer }
                  failed: { type: integer }
                  failures:
                    type: array
                    items:
                      type: object
                      properties:
                        settlement_source_event_id: { type: string }
                        message: { type: string }

  /v1/superadmin/settlement/maintenance/replay-handoffs:
    post:
      summary: Replay queued or failed settlement handoffs
      x-compatibility-class: A
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                limit: { type: integer, minimum: 1, maximum: 1000 }
      responses:
        "200":
          description: Settlement handoff replay summary
          content:
            application/json:
              schema:
                type: object
                properties:
                  scanned: { type: integer }
                  replayed: { type: integer }
                  skipped: { type: integer }
                  failed: { type: integer }
                  failures:
                    type: array
                    items:
                      type: object
                      properties:
                        handoff_id: { type: string }
                        message: { type: string }

  /v1/superadmin/xapps/unscoped:
    get:
      summary: List xapps without tenant scope
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Unscoped xapps and install-based tenant inference summary
          content:
            application/json:
              schema:
                type: object
                properties:
                  summary: { type: object }
                  items:
                    type: array
                    items: { type: object }

  /v1/superadmin/publishers/{id}:
    patch:
      summary: Update publisher
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                name: { type: string }
                slug: { type: string }
                type: { type: string }
                client_id: { type: string, nullable: true }
                status: { type: string }
                logo_url: { type: string, format: uri }
                details_jsonb: { type: object, additionalProperties: true }
                payment_provider_credentials_refs:
                  type: object
                  additionalProperties:
                    type: object
                    additionalProperties: { type: string }
                payment_provider_secret_refs:
                  type: object
                  additionalProperties:
                    type: object
                    additionalProperties: { type: string }
      responses:
        "200":
          description: Updated

  /v1/superadmin/publishers/{id}/egress-targets:
    get:
      summary: List publisher egress targets (superadmin scope)
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Egress targets list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/PublisherEgressTarget" }

    post:
      summary: Create publisher egress target (superadmin scope)
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PublisherEgressTargetInput" }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  item: { $ref: "#/components/schemas/PublisherEgressTarget" }

  /v1/superadmin/publishers/{id}/egress-targets/{targetId}:
    patch:
      summary: Update publisher egress target (superadmin scope)
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
        - name: targetId
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/PublisherEgressTargetInput" }
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  item: { $ref: "#/components/schemas/PublisherEgressTarget" }

    delete:
      summary: Delete publisher egress target (superadmin scope)
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
        - name: targetId
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Deleted

  /v1/superadmin/publishers/{id}/egress-audit-logs:
    get:
      summary: List publisher egress audit logs (superadmin scope)
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
        - name: targetId
          in: query
          schema: { type: string }
        - name: toolName
          in: query
          schema: { type: string }
        - name: statusFamily
          in: query
          schema: { type: string, enum: ["2xx", "3xx", "4xx", "5xx"] }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 500 }
      responses:
        "200":
          description: Egress audit logs
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/PublisherEgressAuditLog" }

  /v1/superadmin/xapps:
    get:
      summary: List all system xapps
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Xapps list

  /v1/superadmin/xapps/{id}:
    patch:
      summary: Update system xapp
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                name: { type: string }
                slug: { type: string }
                visibility: { type: string }
                status: { type: string }
      responses:
        "200":
          description: Updated

  /v1/superadmin/xapp-versions:
    get:
      summary: List all system xapp versions
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Versions list

  /v1/superadmin/xapp-versions/{id}:
    get:
      summary: Get system xapp version
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Version
          content:
            application/json:
              schema:
                type: object
                properties:
                  version: { $ref: "#/components/schemas/XappVersion" }

    patch:
      summary: Update system xapp version
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                status: { type: string }
      responses:
        "200":
          description: Updated

  /v1/superadmin/xapp-versions/{id}/endpoints:
    get:
      summary: List endpoints for a version (superadmin scope)
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Endpoints list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/XappEndpoint" }

    post:
      summary: Create endpoint for a version (superadmin scope)
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [env, baseUrl]
              additionalProperties: false
              properties:
                env: { type: string }
                baseUrl: { type: string }
                timeoutMs: { type: number }
                healthCheck:
                  type: object
                  additionalProperties: false
                  properties:
                    path: { type: string }
                    intervalS: { type: number }
                retryPolicy:
                  type: object
                  additionalProperties: false
                  properties:
                    maxRetries: { type: number }
                    backoff: { type: string, enum: [none, linear, exponential] }
                region: { type: string }
                labels:
                  type: object
                  additionalProperties: { type: string }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { $ref: "#/components/schemas/XappEndpoint" }

  /v1/superadmin/endpoints/{id}:
    patch:
      summary: Update endpoint metadata/configuration (superadmin scope)
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                env: { type: string }
                baseUrl: { type: string }
                timeoutMs: { type: number }
                healthCheck:
                  type: object
                  additionalProperties: false
                  properties:
                    path: { type: string }
                    intervalS: { type: number }
                retryPolicy:
                  type: object
                  additionalProperties: false
                  properties:
                    maxRetries: { type: number }
                    backoff: { type: string, enum: [none, linear, exponential] }
                region: { type: string }
                labels:
                  type: object
                  additionalProperties: { type: string }
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  endpoint: { $ref: "#/components/schemas/XappEndpoint" }

  /v1/superadmin/endpoints/{id}/credentials:
    get:
      summary: List credentials for an endpoint (superadmin scope)
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Credentials list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/EndpointCredential" }

    post:
      summary: Create endpoint credential (superadmin scope)
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [authType]
              additionalProperties: false
              properties:
                authType:
                  type: string
                  enum:
                    [
                      hmac,
                      mtls,
                      oauth2_cc,
                      api_key_header,
                      none,
                      oauth2,
                      bearer,
                      header,
                      query,
                      api-key,
                      apikey,
                    ]
                config:
                  $ref: "#/components/schemas/EndpointCredentialConfig"
                initialKey:
                  type: object
                  additionalProperties: false
                  properties:
                    secret: { type: string }
                    secretRef:
                      type: string
                      description: "Secret reference (supported: env:NAME; vault://... when VAULT_ADDR + VAULT_TOKEN are configured; awssm://... when AWS_REGION + AWS credentials are configured)"
                    status: { type: string }
                    algorithm: { type: string, enum: [hmac-sha256, hmac-sha512, ed25519] }
                    notBefore: { type: string, format: date-time }
                    notAfter: { type: string, format: date-time }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  credential: { $ref: "#/components/schemas/EndpointCredential" }

  /v1/superadmin/credentials/{id}:
    patch:
      summary: Update endpoint credential auth/config (superadmin scope)
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                authType:
                  type: string
                  enum:
                    [
                      hmac,
                      mtls,
                      oauth2_cc,
                      api_key_header,
                      none,
                      oauth2,
                      bearer,
                      header,
                      query,
                      api-key,
                      apikey,
                    ]
                config:
                  $ref: "#/components/schemas/EndpointCredentialConfig"
      responses:
        "200":
          description: Updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  credential: { $ref: "#/components/schemas/EndpointCredential" }

  /v1/superadmin/credentials/{id}/keys:
    get:
      summary: List credential keys (superadmin scope)
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Keys list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/CredentialKey" }

    post:
      summary: Create credential key (superadmin scope)
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                secret: { type: string }
                secretRef:
                  type: string
                  description: "Secret reference (supported: env:NAME; vault://... when VAULT_ADDR + VAULT_TOKEN are configured; awssm://... when AWS_REGION + AWS credentials are configured)"
                status: { type: string }
                algorithm: { type: string, enum: [hmac-sha256, hmac-sha512, ed25519] }
                notBefore: { type: string, format: date-time }
                notAfter: { type: string, format: date-time }
                makeActive: { type: boolean }
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                type: object
                properties:
                  key: { $ref: "#/components/schemas/CredentialKey" }

  /v1/superadmin/xapp-tools:
    get:
      summary: List all system xapp tools
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Tools list

  /v1/superadmin/xapp-widgets:
    get:
      summary: List all system xapp widgets
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Widgets list

  /v1/superadmin/installations:
    get:
      summary: List all system installations
      tags: [Superadmin]
      description: |
        Supports superadmin and tenant bearer sessions.
        Tenant role is restricted to installations for its own client.
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Installations list

  /v1/superadmin/installations/{id}:
    patch:
      summary: Update system installation
      tags: [Superadmin]
      description: |
        Supports superadmin and tenant bearer sessions.
        Tenant role can update only installations that belong to its own client.
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                status: { type: string }
                versionStrategy: { type: string }
                pinnedVersionId: { type: string, nullable: true }
      responses:
        "200":
          description: Updated

  /v1/superadmin/requests:
    get:
      summary: List all system requests
      tags: [Superadmin]
      description: |
        Supports superadmin and tenant bearer sessions.
        Tenant role is restricted to requests for its own client.
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: clientId
          in: query
          schema: { type: string }
        - name: installationId
          in: query
          schema: { type: string }
        - name: xappId
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string }
        - name: toolName
          in: query
          schema: { type: string }
      responses:
        "200":
          description: Requests list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        status: { type: string }
                        tool_name: { type: string }
                        guard_summary:
                          $ref: "#/components/schemas/GuardSummary"
                        usage_credit:
                          allOf:
                            - $ref: "#/components/schemas/PaymentUsageCreditSummary"
                          nullable: true
              examples:
                requestsListWithGuardSummary:
                  value:
                    items:
                      - id: req_admin_01
                        status: REJECTED
                        tool_name: echo
                        guard_summary:
                          trigger: "before:tool_run"
                          outcome: blocked
                          slug: tenant-delegated-payment-policy
                          reason: payment_not_settled
                          monetization_state:
                            entitlement_state: unknown
                            balance_state: unknown
                            payment_attempt_state: failed
                            issuer: tenant_delegated
                            authority_lane: tenant_delegated
                            source_ref: tenant-policy/delegated-default
                          action:
                            kind: complete_payment

  /v1/superadmin/requests/{id}:
    get:
      summary: Get system request details
      tags: [Superadmin]
      description: |
        Supports superadmin and tenant bearer sessions.
        Tenant role can read only requests for its own client.
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Request details
          content:
            application/json:
              schema:
                type: object
                properties:
                  request:
                    type: object
                    properties:
                      id: { type: string }
                      payload:
                        type: object
                        additionalProperties: true
                      guard_summary:
                        $ref: "#/components/schemas/GuardSummary"
                      usage_credit:
                        allOf:
                          - $ref: "#/components/schemas/PaymentUsageCreditSummary"
                        nullable: true
              examples:
                blockedRequestWithGuardSummary:
                  summary: "Superadmin request detail includes redacted guard summary"
                  value:
                    request:
                      id: req_admin_01
                      payload:
                        companyCui: "RO99887766"
                        requestPurpose: "care_coordination"
                        subject_profiles:
                          identity_basic:
                            name: "Jamie Example"
                            email: "jamie@example.com"
                          billing_business:
                            company_name: "Care Example SRL"
                            company_identification_number: "99887766"
                      guard_summary:
                        trigger: "before:tool_run"
                        outcome: blocked
                        slug: publisher-delegated-payment-policy
                        reason: payment_not_settled
                        monetization_state:
                          entitlement_state: unknown
                          balance_state: unknown
                          payment_attempt_state: failed
                          issuer: publisher_delegated
                          authority_lane: publisher_delegated
                          source_ref: publisher-policy/delegated-default
                        action:
                          kind: complete_payment

  /v1/superadmin/requests/{id}/replay-response-finalized-invoice-hooks:
    post:
      summary: Replay finalized-response invoice hooks for a completed request
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Finalized-response invoice hooks replayed
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: Replay not allowed for the current request state

  /v1/superadmin/artifacts:
    get:
      summary: List all system artifacts
      tags: [Superadmin]
      description: |
        Supports superadmin and tenant bearer sessions.
        Tenant role is restricted to artifacts under requests for its own client.
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: requestId
          in: query
          schema: { type: string }
      responses:
        "200":
          description: Artifacts list

  /v1/superadmin/subjects:
    get:
      summary: List all subjects
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: clientId
          in: query
          schema: { type: string }
        - name: search
          in: query
          schema: { type: string }
      responses:
        "200":
          description: Subjects list

  /v1/superadmin/subjects/{id}:
    get:
      summary: Get subject details
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Subject details

  /v1/superadmin/subject-keys:
    get:
      summary: List all subject keys
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: clientId
          in: query
          schema: { type: string }
        - name: installationId
          in: query
          schema: { type: string }
        - name: subjectId
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string }
      responses:
        "200":
          description: Subject keys list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/SubjectKey" }

  /v1/superadmin/subject-keys/{id}:
    get:
      summary: Get subject key details
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Subject key details

    delete:
      summary: Revoke a subject key
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Key revoked

  /v1/superadmin/subject-keys/revoke-global:
    delete:
      summary: Revoke all subject keys globally (emergency)
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: All keys revoked

  /v1/superadmin/installations/{id}/subject-keys:
    delete:
      summary: Revoke all subject keys for an installation
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Keys revoked

  /v1/superadmin/platform-keys:
    get:
      summary: List platform keys
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Keys list

  /v1/superadmin/platform-keys/rotate:
    post:
      summary: Rotate a platform key
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [purpose]
              additionalProperties: false
              properties:
                purpose: { type: string }
                notAfter: { type: string, format: date-time }
      responses:
        "201":
          description: Rotated

  /v1/superadmin/platform-keys/{id}/disable:
    post:
      summary: Disable a non-active platform key
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Key disabled

  /v1/superadmin/platform-keys/{id}/revoke:
    post:
      summary: Revoke a non-active platform key
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Key revoked

  /v1/superadmin/platform-settings:
    get:
      summary: Get platform-managed payment and invoice settings
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Platform settings snapshot
          content:
            application/json:
              schema:
                type: object
                properties:
                  platform_settings:
                    type: object
    patch:
      summary: Update platform-managed payment and invoice settings
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                details_jsonb: { type: object }
                payment_ui: { type: object }
                payment_provider_credentials_refs: { type: object }
                payment_provider_secret_refs: { type: object }
                invoice_ui: { type: object }
                invoice_provider_credentials_refs: { type: object }
                invoice_provider_secret_refs: { type: object }
                invoice_template_overrides: { type: object }
      responses:
        "200":
          description: Updated platform settings

  /v1/superadmin/events:
    get:
      summary: List system-wide events
      tags: [Superadmin]
      description: |
        Lists top-level platform events and, when `evidenceType` filters are used, may also return request-derived
        evidence rows (for example `REQUEST_EVIDENCE` wrappers for request event evidence).
        Aggregated request-derived evidence row IDs are list-only identifiers and are not guaranteed to resolve via
        `/v1/superadmin/events/{id}`.
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: type
          in: query
          schema: { type: string }
        - name: clientId
          in: query
          schema: { type: string }
        - name: installationId
          in: query
          schema: { type: string }
        - name: xappId
          in: query
          schema: { type: string }
        - name: trigger
          in: query
          schema:
            type: string
            enum:
              [
                before:installation_create,
                before:widget_load,
                before:session_open,
                before:tool_run,
                before:thread_create,
                after:install,
                after:link_complete,
                before:link_revoke,
                before:uninstall,
                after:tool_complete,
                after:uninstall,
              ]
        - name: outcome
          in: query
          schema: { type: string, enum: [pass, blocked] }
        - name: guardSlug
          in: query
          schema: { type: string }
        - name: evidenceType
          in: query
          schema:
            type: string
            enum:
              [
                GUARD_DECISION,
                DISPATCH_SELECTED,
                CREDENTIAL_BOUND,
                SECRET_RESOLVED,
                PROJECTION_APPLIED,
              ]
        - name: endpointRef
          in: query
          schema: { type: string }
        - name: authLane
          in: query
          schema: { type: string }
        - name: credentialProfile
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 200 }
        - name: cursor
          in: query
          schema: { type: string }
      responses:
        "200":
          description: Events list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/Event" }
              examples:
                topLevelGuardPassSessionOpen:
                  summary: "Top-level guard event (superadmin scope) for before:session_open pass"
                  value:
                    items:
                      - id: evt_admin_top_guard_01
                        type: xapps.guard.evaluated
                        client_id: client_01
                        installation_id: inst_01
                        xapp_id: xapp_01
                        occurred_at: "2026-02-21T12:01:00.000Z"
                        created_at: "2026-02-21T12:01:01.000Z"
                        payload:
                          trigger: "before:session_open"
                          outcome: pass
                          mode: all
                          on_fail: stop_on_fail
                          monetization_verified:
                            - guard_slug: subscription-check
                              kind: subscription
                              monetization_state:
                                entitlement_state: active
                                balance_state: unknown
                                payment_attempt_state: unknown
                                tier: enterprise
                                issuer: tenant
                                authority_lane: owner_managed
                                source_ref: tenant-policy/subscription-default
                          monetization_state:
                            entitlement_state: active
                            balance_state: unknown
                            payment_attempt_state: unknown
                            tier: enterprise
                            issuer: tenant
                            authority_lane: owner_managed
                            source_ref: tenant-policy/subscription-default
                topLevelGuardPassSessionOpenCredits:
                  summary: "Top-level guard event (superadmin scope) for before:session_open credits pass"
                  value:
                    items:
                      - id: evt_admin_top_guard_credits_01
                        type: xapps.guard.evaluated
                        client_id: client_01
                        installation_id: inst_01
                        xapp_id: xapp_01
                        occurred_at: "2026-02-21T12:04:00.000Z"
                        created_at: "2026-02-21T12:04:01.000Z"
                        payload:
                          trigger: "before:session_open"
                          outcome: pass
                          mode: all
                          on_fail: stop_on_fail
                          monetization_verified:
                            - guard_slug: credit-balance-check
                              kind: credits
                              monetization_state:
                                entitlement_state: unknown
                                balance_state: sufficient
                                payment_attempt_state: unknown
                                issuer: tenant
                                authority_lane: owner_managed
                                source_ref: tenant-policy/credits-default
                          monetization_state:
                            entitlement_state: unknown
                            balance_state: sufficient
                            payment_attempt_state: unknown
                            issuer: tenant
                            authority_lane: owner_managed
                            source_ref: tenant-policy/credits-default
                evidenceFilteredDispatchEvents:
                  summary: "Evidence-filtered dispatch events (superadmin scope)"
                  value:
                    items:
                      - id: evt_admin_01
                        type: REQUEST_EVIDENCE
                        client_id: client_01
                        installation_id: inst_01
                        xapp_id: xapp_01
                        occurred_at: "2026-02-21T12:00:00.000Z"
                        created_at: "2026-02-21T12:00:01.000Z"
                        payload:
                          evidence_type: DISPATCH_SELECTED
                          endpoint_ref: prod
                          auth_lane: hmac
                          credential_profile: primary
                          request_id: req_admin_01
                evidenceFilteredGuardEvents:
                  summary: "Evidence-filtered guard events with redacted monetization summary (superadmin scope)"
                  value:
                    items:
                      - id: evt_admin_guard_01
                        type: REQUEST_EVIDENCE
                        client_id: client_01
                        installation_id: inst_01
                        xapp_id: xapp_01
                        occurred_at: "2026-02-21T12:02:00.000Z"
                        created_at: "2026-02-21T12:02:01.000Z"
                        payload:
                          evidence_type: GUARD_EVALUATED
                          request_id: req_admin_02
                          guard:
                            slug: tenant-payment-policy
                            trigger: "before:tool_run"
                            outcome: blocked
                            reason: payment_not_settled
                          monetization_state:
                            payment_attempt_state: failed
                            entitlement_state: unknown
                            balance_state: unknown
                      - id: evt_admin_guard_02
                        type: REQUEST_EVIDENCE
                        client_id: client_01
                        installation_id: inst_01
                        xapp_id: xapp_01
                        occurred_at: "2026-02-21T12:03:00.000Z"
                        created_at: "2026-02-21T12:03:01.000Z"
                        payload:
                          evidence_type: GUARD_EVALUATED
                          request_id: req_admin_03
                          outcome: pass
                          monetization_verified:
                            - guard_slug: credit-balance-check
                              kind: credits
                              monetization_state:
                                entitlement_state: active
                                balance_state: sufficient
                                payment_attempt_state: unknown
                                tier: pro
                                issuer: tenant
                          monetization_state:
                            entitlement_state: active
                            balance_state: sufficient
                            payment_attempt_state: unknown
                            tier: pro
                            issuer: tenant

  /v1/superadmin/invoices:
    get:
      summary: List invoices across the platform or an admin scope
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: clientId
          in: query
          schema: { type: string }
        - name: requestId
          in: query
          schema: { type: string }
        - name: paymentSessionId
          in: query
          schema: { type: string }
        - name: xappId
          in: query
          schema: { type: string }
        - name: ownerScope
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string }
        - name: search
          in: query
          schema: { type: string }
        - name: page
          in: query
          schema: { type: integer, minimum: 1 }
        - name: pageSize
          in: query
          schema: { type: integer, minimum: 1, maximum: 100 }
      responses:
        "200":
          description: Admin invoice list
          content:
            application/json:
              schema:
                type: object
                required: [items]
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/PortalInvoiceSummary"
                  pagination:
                    type: object
                    properties:
                      total: { type: integer }
                      page: { type: integer }
                      pageSize: { type: integer }
                      totalPages: { type: integer }

  /v1/superadmin/invoices/{id}/history:
    get:
      summary: Get invoice lifecycle history
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Invoice lifecycle history
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/InvoiceHistoryResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

  /v1/superadmin/invoices/{id}/void:
    post:
      summary: Void an issued invoice and create a compensating reversal record
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                reason: { type: string, nullable: true }
      responses:
        "200":
          description: Invoice voided and reversal created
          content:
            application/json:
              schema:
                type: object
                properties:
                  item:
                    allOf:
                      - $ref: "#/components/schemas/PortalInvoiceSummary"
                    nullable: true
                  reversal:
                    allOf:
                      - $ref: "#/components/schemas/PortalInvoiceSummary"
                    nullable: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"

  /v1/superadmin/events/{id}:
    get:
      summary: Get event details and deliveries
      tags: [Superadmin]
      description: |
        Returns a top-level event record and its deliveries for a canonical `events.id`.
        IDs returned by evidence-filtered aggregated request-evidence rows from `/v1/superadmin/events` may not be
        canonical event IDs and can return `404` here.
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Event details
          content:
            application/json:
              schema:
                type: object
                properties:
                  event: { $ref: "#/components/schemas/Event" }
                  deliveries:
                    type: array
                    items: { $ref: "#/components/schemas/EventDelivery" }

  /v1/superadmin/event-deliveries:
    get:
      summary: List all event deliveries
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: eventId
          in: query
          schema: { type: string }
        - name: installationId
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string, enum: [pending, completed, failed, dead_letter] }
      responses:
        "200":
          description: Deliveries list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/EventDelivery" }

  /v1/superadmin/event-deliveries/{id}/redeliver:
    post:
      summary: Manually trigger redelivery of an event
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: OK

  /v1/superadmin/secrets:
    get:
      summary: List platform secrets (superadmin scope)
      operationId: listPlatformSecrets
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: scope
          in: query
          schema: { type: string }
        - name: scopeId
          in: query
          schema: { type: string }
        - name: tag
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { type: object }
    post:
      summary: Create platform secret (superadmin scope)
      operationId: createPlatformSecret
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string }
                scope: { type: string }
                scopeId: { type: string }
                tags:
                  type: array
                  items: { type: string }
                secret: { type: string }
                secretRef: { type: string }
                description: { type: string }
                expiresAt: { type: string, format: date-time }
      responses:
        "201":
          description: Created

  /v1/superadmin/secrets/{id}:
    get:
      summary: Get platform secret by ID (superadmin scope)
      operationId: getPlatformSecret
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: OK
    patch:
      summary: Update platform secret (superadmin scope)
      operationId: updatePlatformSecret
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string }
                tags:
                  type: array
                  items: { type: string }
                secret: { type: string }
                secretRef: { type: string }
                description: { type: string }
                status: { type: string }
                expiresAt: { type: string, format: date-time, nullable: true }
      responses:
        "200":
          description: Updated
    delete:
      summary: Delete platform secret (superadmin scope)
      operationId: deletePlatformSecret
      tags: [Superadmin]
      security:
        - SuperadminKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: OK

  /embed/widgets/{widgetId}:
    get:
      summary: Serve widget host page
      tags: [Embed]
      parameters:
        - name: widgetId
          in: path
          required: true
          schema: { type: string }
        - name: token
          in: query
          schema: { type: string }
      responses:
        "200":
          description: HTML
          content:
            text/html:
              schema: { type: string }

  /embed/catalog:
    get:
      summary: Serve embedded Marketplace Catalog SPA shell
      tags: [Embed]
      description: |
        Serves the HTML shell for the embedded catalog React app.

        ### Bridge Protocol (postMessage)
        The embedded catalog communicates with the host application via `window.parent.postMessage`.
        See `docs/06-EMBEDDING_GUIDE.md` for a detailed description of the bridge protocol and events.

        #### Events sent from Catalog to Host:
        - `XAPPS_MARKETPLACE_REQUEST_INSTALL`: `{ xappId, subjectId }`
        - `XAPPS_MARKETPLACE_REQUEST_UNINSTALL`: `{ installationId }`
        - `XAPPS_OPEN_WIDGET`: `{ installationId, widgetId }`
        - `XAPPS_IFRAME_RESIZE`: `{ height }`

        #### Events sent from Host to Catalog:
        - `XAPPS_MARKETPLACE_INSTALL_SUCCESS`: `{ xappId, installationId }`
        - `XAPPS_MARKETPLACE_INSTALL_FAILURE`: `{ xappId, message }`

        Notes:
        - This endpoint also serves all sub-paths under `/embed/catalog/*` for history routing.
        - Requires a `token` query parameter containing a valid catalog session token.
      parameters:
        - name: token
          in: query
          required: true
          schema: { type: string }
      responses:
        "200":
          description: HTML
          content:
            text/html:
              schema: { type: string }
        "401":
          description: Invalid or missing token

  /embed/catalog/{wildcard}:
    get:
      summary: Serve embedded Marketplace Catalog SPA shell (history route)
      tags: [Embed]
      description: |
        Catch-all history route for SPA deep links.
        Behavior matches `/embed/catalog`.
      parameters:
        - name: wildcard
          in: path
          required: true
          schema: { type: string }
        - name: token
          in: query
          required: true
          schema: { type: string }
      responses:
        "200":
          description: HTML
          content:
            text/html:
              schema: { type: string }
        "401":
          description: Invalid or missing token

  /embed/assets/catalog.js:
    get:
      summary: Embedded catalog JS bundle
      tags: [Embed]
      responses:
        "200":
          description: JavaScript
          content:
            application/javascript:
              schema: { type: string }

  /embed/assets/catalog.css:
    get:
      summary: Embedded catalog CSS bundle
      tags: [Embed]
      responses:
        "200":
          description: CSS
          content:
            text/css:
              schema: { type: string }

  /embed/catalog-data:
    get:
      summary: List catalog xapps (embed-safe)
      tags: [Embed]
      description: |
        Safe read-only catalog listing for the embedded catalog iframe.
        Authorized via a catalog session token passed as query string.
      parameters:
        - name: token
          in: query
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Xapps list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/Xapp" }
        "401":
          description: Invalid or missing token

  /embed/catalog-xapps/{xappId}:
    get:
      summary: Get xapp details (embed-safe)
      tags: [Embed]
      description: |
        Safe read-only xapp detail for the embedded catalog iframe.
        Authorized via a catalog session token passed as query string.

        Notes:
        - `widgets` may be a minimal projection intended for the embedded Marketplace UI.
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: token
          in: query
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Xapp details
          content:
            application/json:
              schema:
                type: object
                properties:
                  xapp: { $ref: "#/components/schemas/Xapp" }
                  version: { $ref: "#/components/schemas/XappVersion" }
                  manifest:
                    type: object
                    nullable: true
                  tools:
                    type: array
                    items: { $ref: "#/components/schemas/XappTool" }
                  widgets:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        default: { type: boolean }
                  operational_surfaces:
                    $ref: "#/components/schemas/OperationalSurfacesDescriptor"
        "401":
          description: Invalid or missing token
        "404":
          description: Xapp not found

  /embed/my-requests:
    get:
      summary: List subject requests (embed-safe, read-only)
      tags: [Embed]
      description: |
        Lists requests for the `subjectId` bound to the catalog token.

        Requirements:
        - Valid catalog token in `token` query param.
        - Token must include a `subjectId` (otherwise returns `403 Subject required`).
      parameters:
        - name: token
          in: query
          required: true
          schema: { type: string }
        - name: xappId
          in: query
          schema: { type: string }
        - name: toolName
          in: query
          schema: { type: string }
        - name: page
          in: query
          schema: { type: integer, minimum: 1 }
        - name: pageSize
          in: query
          schema: { type: integer, minimum: 1, maximum: 100 }
      responses:
        "200":
          description: Requests list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
                  pagination:
                    type: object
                    properties:
                      total: { type: integer }
                      page: { type: integer }
                      pageSize: { type: integer }
                      totalPages: { type: integer }
        "401":
          description: Invalid or missing token
        "403":
          description: Subject required
          content:
            application/json:
              schema:
                type: object
                properties:
                  message: { type: string, example: "Subject required" }

  /embed/my-requests/{id}:
    get:
      summary: Get subject request detail (embed-safe, read-only)
      tags: [Embed]
      description: |
        Returns request detail for the `subjectId` bound to the catalog token.

        Requirements:
        - Valid catalog token in `token` query param.
        - Token must include a `subjectId` (otherwise returns `403 Subject required`).
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
        - name: token
          in: query
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Request detail
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        "401":
          description: Invalid or missing token
        "403":
          description: Subject required
          content:
            application/json:
              schema:
                type: object
                properties:
                  message: { type: string, example: "Subject required" }
        "404":
          description: Request not found

  /embed/my-requests/{id}/payment/reconcile:
    post:
      summary: Reconcile payment finality for an embed-scoped request
      tags: [Embed]
      x-compatibility-class: A
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
        - name: token
          in: query
          required: true
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RequestPaymentReconcileRequest"
      responses:
        "200":
          description: Reconcile accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RequestPaymentReconcileResponse"
        "401":
          description: Invalid or missing token
        "403":
          description: Subject required
        "404":
          description: Request or payment session not found
        "409":
          description: Payment session is missing on request guard context, or provider does not support reconciliation
        "500":
          description: Reconcile misconfigured

  /embed/my-xapps/{xappId}/monetization:
    get:
      summary: Get current-user monetization state for an embedded xapp
      tags: [Embed]
      description: |
        Returns current-user monetization state for the published xapp bound to the catalog token.

        The response carries the same current-user paywall and access truth used by the
        portal/shared marketplace lane:
        - current access projection
        - current subscription summary
        - additive entitlement summaries such as durable unlocks
        - resolved paywall projections from the published manifest

        `installationId` is optional app context. When present, it is validated against the
        current client/xapp/subject, but the monetization read remains subject-scoped.
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: token
          in: query
          required: true
          schema: { type: string }
        - name: installationId
          in: query
          required: false
          schema: { type: string }
        - name: locale
          in: query
          required: false
          schema: { type: string }
        - name: country
          in: query
          required: false
          schema: { type: string }
      responses:
        "200":
          description: Embedded current-user monetization state for the selected published xapp.
          content:
            application/json:
              schema: { type: object }
        "401":
          description: Invalid or missing token
        "403":
          description: Subject required
        "404":
          description: Xapp or published version not found

  /embed/my-xapps/{xappId}/monetization/history:
    get:
      summary: Get current-user monetization history for an embedded xapp
      tags: [Embed]
      description: |
        Returns grouped current-user monetization history for the published xapp bound to the
        catalog token.

        Current history buckets include:
        - purchase intents
        - transactions
        - subscriptions
        - entitlements
        - wallet accounts
        - wallet ledger
        - access snapshots
        - correlated invoices
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: token
          in: query
          required: true
          schema: { type: string }
        - name: limit
          in: query
          required: false
          schema: { type: integer, minimum: 1, maximum: 100, default: 10 }
      responses:
        "200":
          description: Embedded current-user monetization history grouped for the selected published xapp.
          content:
            application/json:
              schema: { type: object }
        "401":
          description: Invalid or missing token
        "403":
          description: Subject required
        "404":
          description: Xapp or published version not found

  /embed/my-xapps/{xappId}/monetization/subscription-contracts/{contractId}/cancel:
    post:
      summary: Cancel renewal for the embedded current-user xapp subscription contract
      tags: [Embed]
      description: |
        Cancels renewal for the selected embedded current-user subscription contract and returns
        the refreshed current subscription plus access projection for the same token-bound subject lane.
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: contractId
          in: path
          required: true
          schema: { type: string }
        - name: token
          in: query
          required: true
          schema: { type: string }
        - name: installationId
          in: query
          required: false
          schema: { type: string }
      responses:
        "200":
          description: Updated embedded current-user subscription lifecycle after cancelling renewal for the selected contract.
          content:
            application/json:
              schema: { type: object }
        "401":
          description: Invalid or missing token
        "403":
          description: Subject required
        "404":
          description: Xapp, subscription contract, or published version not found

  /embed/my-xapps/{xappId}/monetization/subscription-contracts/{contractId}/refresh-state:
    post:
      summary: Refresh the embedded current-user xapp subscription lifecycle state
      tags: [Embed]
      description: |
        Refreshes the selected embedded current-user subscription contract and returns
        refreshed current subscription plus access projection for the same token-bound subject lane.
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: contractId
          in: path
          required: true
          schema: { type: string }
        - name: token
          in: query
          required: true
          schema: { type: string }
        - name: installationId
          in: query
          required: false
          schema: { type: string }
      responses:
        "200":
          description: Updated embedded current-user subscription lifecycle after refreshing the selected contract.
          content:
            application/json:
              schema: { type: object }
        "401":
          description: Invalid or missing token
        "403":
          description: Subject required
        "404":
          description: Xapp, subscription contract, or published version not found

  /embed/my-monetization/xapps:
    get:
      summary: Get current-user monetization overview across embedded xapps
      tags: [Embed]
      description: |
        Returns the current-user monetization overview grouped by xapp for the subject bound to
        the current catalog token.

        By default only current/active monetized xapps are returned. Set `current_only=false`
        to include past monetization records as well.
      parameters:
        - name: token
          in: query
          required: true
          schema: { type: string }
        - name: currentOnly
          in: query
          required: false
          schema: { type: boolean, default: true }
      responses:
        "200":
          description: Embedded current-user monetization overview grouped across xapps.
          content:
            application/json:
              schema: { type: object }
        "401":
          description: Invalid or missing token
        "403":
          description: Subject required

  /embed/my-xapps/{xappId}/monetization/purchase-intents/prepare:
    post:
      summary: Prepare a current-user purchase intent for an embedded xapp package
      tags: [Embed]
      description: |
        Prepares a current-user monetization purchase intent for a published embedded xapp package.

        `installation_id` may be provided as app context and is validated against the current
        subject/client/xapp installation, but the resulting monetization intent remains single-scope.
        `locale` and `country` may be supplied to evaluate offering/paywall targeting and
        price country rules on the same gateway lane.
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: token
          in: query
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object }
      responses:
        "200":
          description: Prepared purchase intent for the selected embedded package.
          content:
            application/json:
              schema: { type: object }
        "401":
          description: Invalid or missing token
        "403":
          description: Subject required
        "404":
          description: Xapp or published version not found

  /embed/my-xapps/{xappId}/monetization/purchase-intents/{intentId}/payment-session:
    post:
      summary: Create a hosted payment session for an embedded current-user purchase intent
      tags: [Embed]
      description: |
        Creates a hosted payment session for a prepared embedded current-user monetization intent,
        using the same xapp payment definition / payment-guard selection seam as the portal lane.
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: intentId
          in: path
          required: true
          schema: { type: string }
        - name: token
          in: query
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object }
      responses:
        "200":
          description: Hosted payment session plus payment page URL for the prepared embedded intent.
          content:
            application/json:
              schema: { type: object }
        "401":
          description: Invalid or missing token
        "403":
          description: Subject required
        "404":
          description: Purchase intent, xapp, or published version not found

  /embed/my-xapps/{xappId}/monetization/purchase-intents/{intentId}/payment-session/finalize:
    post:
      summary: Finalize an embedded hosted payment session into XMS access state
      tags: [Embed]
      description: |
        Reconciles the current hosted payment session for a prepared embedded current-user purchase
        intent and issues/refetches XMS access state on the same embedded current-user lane used by
        the shared marketplace/embed surfaces.
      parameters:
        - name: xappId
          in: path
          required: true
          schema: { type: string }
        - name: intentId
          in: path
          required: true
          schema: { type: string }
        - name: token
          in: query
          required: true
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema: { type: object }
      responses:
        "200":
          description: Finalized hosted payment session plus refreshed XMS access artifacts for the embedded current-user lane.
          content:
            application/json:
              schema: { type: object }
        "400":
          description: Purchase intent is not linked to a payment session
        "401":
          description: Invalid or missing token
        "403":
          description: Subject required
        "404":
          description: Purchase intent, xapp, published version, or payment session not found
        "409":
          description: Payment session not paid yet or access issuance is not yet active for the product family

  /embed/my-payment-sessions:
    get:
      summary: List subject payment sessions (embed-safe, read-only)
      tags: [Embed]
      description: |
        Lists payment sessions for the `subjectId` bound to the catalog token.
      parameters:
        - name: token
          in: query
          required: true
          schema: { type: string }
        - name: paymentSessionId
          in: query
          schema: { type: string }
        - name: xappId
          in: query
          schema: { type: string }
        - name: installationId
          in: query
          schema: { type: string }
        - name: toolName
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string }
        - name: issuer
          in: query
          schema: { type: string }
        - name: createdFrom
          in: query
          schema: { type: string, format: date }
        - name: createdTo
          in: query
          schema: { type: string, format: date }
        - name: limit
          in: query
          schema: { type: integer }
      responses:
        "200":
          description: Payment session list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/PortalPaymentSessionSummary"
        "401":
          description: Invalid or missing token
        "403":
          description: Subject required

  /embed/my-invoices:
    get:
      summary: List subject invoices (embed-safe, read-only)
      tags: [Embed]
      description: |
        Lists invoices for the `subjectId` bound to the catalog token.
      parameters:
        - name: token
          in: query
          required: true
          schema: { type: string }
        - name: requestId
          in: query
          schema: { type: string }
        - name: paymentSessionId
          in: query
          schema: { type: string }
        - name: xappId
          in: query
          schema: { type: string }
        - name: installationId
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string }
        - name: ownerScope
          in: query
          schema: { type: string }
        - name: search
          in: query
          schema: { type: string }
        - name: createdFrom
          in: query
          schema: { type: string, format: date }
        - name: createdTo
          in: query
          schema: { type: string, format: date }
        - name: page
          in: query
          schema: { type: integer, minimum: 1 }
        - name: pageSize
          in: query
          schema: { type: integer, minimum: 1, maximum: 100 }
      responses:
        "200":
          description: Invoice list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/PortalInvoiceSummary"
                  pagination:
                    type: object
                    properties:
                      total: { type: integer }
                      page: { type: integer }
                      pageSize: { type: integer }
                      totalPages: { type: integer }
        "401":
          description: Invalid or missing token
        "403":
          description: Subject required

  /embed/my-payment-sessions/{id}:
    get:
      summary: Get subject payment session details (embed-safe, read-only)
      tags: [Embed]
      description: |
        Returns payment session details for the `subjectId` bound to the catalog token.
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
        - name: token
          in: query
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Payment session details
          content:
            application/json:
              schema:
                type: object
                properties:
                  session:
                    $ref: "#/components/schemas/PortalPaymentSessionSummary"
        "401":
          description: Invalid or missing token
        "403":
          description: Subject required

  /embed/my-notifications:
    get:
      summary: List subject notification activity (embed-safe, read-only)
      tags: [Embed]
      description: |
        Lists notifications for the `subjectId` bound to the catalog token.
      parameters:
        - name: token
          in: query
          required: true
          schema: { type: string }
        - name: notificationId
          in: query
          schema: { type: string }
        - name: status
          in: query
          schema: { type: string }
        - name: trigger
          in: query
          schema: { type: string }
        - name: xappId
          in: query
          schema: { type: string }
        - name: installationId
          in: query
          schema: { type: string }
        - name: unread
          in: query
          schema: { type: boolean }
        - name: limit
          in: query
          schema: { type: integer }
      responses:
        "200":
          description: Notification list
          content:
            application/json:
              schema:
                type: object
                properties:
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/PortalNotificationRecordSummary"
                  unread_count: { type: integer }
        "401":
          description: Invalid or missing token
        "403":
          description: Subject required

  /embed/my-notifications/read-all:
    post:
      summary: Mark all embedded notification inbox items as read
      tags: [Embed]
      parameters:
        - name: token
          in: query
          required: true
          schema: { type: string }
      responses:
        "200":
          description: All embedded notification inbox items marked as read
          content:
            application/json:
              schema:
                type: object
                required: [ok, unread_count]
                properties:
                  ok: { type: boolean }
                  unread_count: { type: integer }
        "401":
          description: Invalid or missing token
        "403":
          description: Subject required

  /embed/my-notifications/{notificationId}/read:
    post:
      summary: Mark one embedded notification inbox item as read
      tags: [Embed]
      parameters:
        - name: notificationId
          in: path
          required: true
          schema: { type: string }
        - name: token
          in: query
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Embedded notification inbox item marked as read
          content:
            application/json:
              schema:
                type: object
                required: [ok, unread_count]
                properties:
                  ok: { type: boolean }
                  unread_count: { type: integer }
        "401":
          description: Invalid or missing token
        "403":
          description: Subject required
        "404":
          description: Notification not found

  /embed/my-invoices/{id}/history:
    get:
      summary: Get subject invoice history (embed-safe, read-only)
      tags: [Embed]
      description: |
        Returns invoice lineage and history for the `subjectId` bound to the catalog token.
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
        - name: token
          in: query
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Invoice history
          content:
            application/json:
              schema:
                type: object
                properties:
                  item:
                    $ref: "#/components/schemas/PortalInvoiceSummary"
                  root_invoice_id:
                    type: string
                    nullable: true
                  lineage:
                    type: array
                    items:
                      $ref: "#/components/schemas/PortalInvoiceSummary"
                  events:
                    type: array
                    items:
                      type: object
                      additionalProperties: true
        "401":
          description: Invalid or missing token
        "403":
          description: Subject required

  /embed/sdk/{filename}:
    get:
      summary: Serve embed SDK bundle
      tags: [Embed]
      description: |
        Serves the browser embed SDK for use in integrator hosts.

        Notes:
        - Intended to be loaded from any origin (CORS `Access-Control-Allow-Origin: *`).
      parameters:
        - name: filename
          in: path
          required: true
          schema: { type: string }
      responses:
        "200":
          description: SDK file
        "404":
          description: SDK file not found

  /embed/sdk.js:
    get:
      summary: Serve embed SDK bundle (convenience alias)
      tags: [Embed]
      description: |
        Convenience alias for `/embed/sdk/sdk.js`.
        Served with CORS `Access-Control-Allow-Origin: *`.
      responses:
        "200":
          description: SDK JavaScript bundle
          content:
            application/javascript:
              schema: { type: string }

  /embed/widget-token:
    get:
      summary: Generate a widget token from a catalog session
      tags: [Embed]
      description: |
        Creates a widget session token for embedding a widget within the catalog context.
        Authorized via a catalog session token passed as query string.
      parameters:
        - name: token
          in: query
          required: true
          schema: { type: string }
        - name: installationId
          in: query
          required: true
          schema: { type: string }
        - name: widgetId
          in: query
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Widget token
          content:
            application/json:
              schema:
                type: object
                properties:
                  token: { type: string }
                  embedUrl: { type: string }
        "401":
          description: Invalid or missing token

  /embed/assets/jsonforms.js:
    get:
      summary: Serve JSONForms library bundle
      tags: [Embed]
      responses:
        "200":
          description: JavaScript
          content:
            application/javascript:
              schema: { type: string }

  /embed/jsonforms.js:
    get:
      summary: Serve JSONForms library bundle (compatibility alias)
      tags: [Embed]
      description: Alias for `/embed/assets/jsonforms.js`.
      responses:
        "200":
          description: JavaScript
          content:
            application/javascript:
              schema: { type: string }

  /embed/marketplace-demo/dashboard:
    get:
      summary: Serve marketplace demo dashboard embed page
      tags: [Embed]
      description: Demo page used by marketplace demo widget scenarios.
      responses:
        "200":
          description: HTML
          content:
            text/html:
              schema: { type: string }

  /embed/ui-kit/preview:
    post:
      summary: Preview a UI Kit widget
      tags: [Embed]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
      responses:
        "200":
          description: Preview HTML
          content:
            text/html:
              schema: { type: string }

  /embed/v1/widget-preview:
    post:
      summary: Preview a widget endpoint (widget token)
      tags: [Embed]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                widgetId: { type: string }
                kind: { type: string }
                endpointKey: { type: string }
                endpointEnv: { type: string }
                payload: { type: object }
      responses:
        "200":
          description: Preview response envelope
          content:
            application/json:
              schema:
                type: object
        "400":
          description: Invalid preview request
        "401":
          description: Missing/invalid widget token
        "403":
          description: Widget token scope mismatch
        "404":
          description: Endpoint not found
        "502":
          description: Upstream endpoint invalid/unavailable
        "504":
          description: Preview timeout

  /embed/upload:
    post:
      summary: Upload artifact metadata (embed)
      tags: [Embed]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                [
                  requestId,
                  kind,
                  filename,
                  mimeType,
                  sizeBytes,
                  storageProvider,
                  storageKey,
                  checksumSha256,
                ]
              additionalProperties: false
              properties:
                requestId: { type: string }
                kind: { type: string }
                filename: { type: string }
                mimeType: { type: string }
                sizeBytes: { type: number }
                storageProvider: { type: string }
                storageKey: { type: string }
                checksumSha256: { type: string }
      responses:
        "201":
          description: Created

  /v1/payment-sessions:
    post:
      summary: Create a gateway-managed payment session (server-to-server)
      x-compatibility-class: A
      tags: [Payments]
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PaymentSessionCreateRequest"
      responses:
        "201":
          description: Payment session created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaymentSessionCreateResponse"
        "400":
          description: Invalid request
        "401":
          description: X-API-Key required

  /v1/payment-sessions/{id}:
    get:
      summary: Read a gateway-managed payment session (server-to-server)
      x-compatibility-class: A
      tags: [Payments]
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: Payment session
          content:
            application/json:
              examples:
                gateway_managed_pending:
                  summary: Gateway-managed pending session
                  value:
                    session:
                      payment_session_id: pay_gw_123
                      client_id: CL001
                      xapp_id: xapp_demo
                      tool_name: purchase
                      amount: "10.00"
                      currency: USD
                      issuer: gateway
                      provider_key: mock_manual
                      status: pending
                      return_url: "https://host.example/return"
                      cancel_url: "https://host.example/cancel"
                      xapps_resume: null
                tenant_delegated_pending:
                  summary: Tenant-delegated pending session with lane metadata
                  value:
                    session:
                      payment_session_id: pay_td_123
                      client_id: CL001
                      xapp_id: xapp_demo
                      tool_name: purchase
                      amount: "12.00"
                      currency: USD
                      issuer: tenant_delegated
                      provider_key: stripe_checkout
                      status: pending
                      return_url: "https://host.example/return"
                      metadata_jsonb:
                        payment_signing:
                          signing_lane: tenant_delegated
                          resolver_source: guard_config_delegated_lane
              schema:
                $ref: "#/components/schemas/PaymentSessionEnvelope"
        "401":
          description: X-API-Key required
        "404":
          description: Payment session not found

  /v1/payment-sessions/{id}/complete:
    post:
      summary: Mark a gateway-managed payment session completed (server-to-server)
      x-compatibility-class: A
      tags: [Payments]
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PaymentSessionCompleteRequest"
      responses:
        "200":
          description: |
            Payment session updated.
            Compatibility class: A (additive). Current runtime returns `session`; additive
            fields (`flow`, `payment_session_id`, `client_settle_url`, `provider_reference`,
            `scheme`, `metadata`) are reserved for provider/client-collect compatible gateways.
          content:
            application/json:
              examples:
                session_only_current_runtime:
                  summary: Current runtime response (session only)
                  value:
                    session:
                      payment_session_id: pay_legacy_123
                      status: completed
                      issuer: gateway
                      updated_at: "2026-02-28T18:00:00.000Z"
                additive_client_collect_compatible:
                  summary: Additive-compatible completion payload shape
                  value:
                    session:
                      payment_session_id: pay_cc_123
                      status: pending
                      issuer: tenant_delegated
                    flow: client_collect
                    payment_session_id: pay_cc_123
                    client_settle_url: "https://gateway.example/v1/gateway-payment/client-settle"
                    provider_reference: cc_pay_cc_123
                    scheme:
                      scheme_key: mock_client_collect
                      adapter_dispatch_key: mock_client_collect
                      supported_flows: [client_collect]
                      status: active
                    metadata:
                      provider_flow: client_collect
              schema:
                $ref: "#/components/schemas/PaymentSessionCompleteResponse"
        "401":
          description: X-API-Key required
        "404":
          description: Payment session not found
        "409":
          description: Payment session already used

  /v1/gateway-payment.html:
    get:
      summary: Gateway-managed payment checkout page
      x-compatibility-class: A
      tags: [Payments]
      parameters:
        - in: query
          name: payment_session_id
          required: true
          schema: { type: string }
        - in: query
          name: return_url
          required: false
          schema: { type: string, format: uri }
        - in: query
          name: cancel_url
          required: false
          schema: { type: string, format: uri }
        - in: query
          name: xapps_resume
          required: false
          schema: { type: string }
      responses:
        "200":
          description: HTML checkout page
          content:
            text/html:
              schema: { type: string }

  /v1/gateway-payment/session:
    get:
      summary: Read gateway checkout session for gateway payment page
      x-compatibility-class: A
      tags: [Payments]
      parameters:
        - in: query
          name: payment_session_id
          required: true
          schema: { type: string }
        - in: query
          name: return_url
          required: false
          schema: { type: string, format: uri }
        - in: query
          name: cancel_url
          required: false
          schema: { type: string, format: uri }
        - in: query
          name: xapps_resume
          required: false
          schema: { type: string }
      responses:
        "200":
          description: Gateway checkout session (browser-safe metadata; provider credential bundles are redacted)
          content:
            application/json:
              examples:
                gateway_managed_checkout:
                  summary: Gateway-managed checkout session read
                  value:
                    result:
                      payment_session_id: pay_gw_123
                      issuer: gateway
                      status: pending
                      amount: "10.00"
                      currency: USD
                      return_url: "https://host.example/return"
                      ui:
                        brand:
                          name: Xapps Payment
                          accent: "#0a7a6b"
                        copy:
                          title: Secure checkout
                          subtitle: Gateway checkout
                publisher_delegated_checkout:
                  summary: Publisher-delegated checkout session read
                  value:
                    result:
                      payment_session_id: pay_pd_123
                      issuer: publisher_delegated
                      status: pending
                      amount: "8.00"
                      currency: USD
                      return_url: "https://host.example/return"
                      metadata_jsonb:
                        payment_signing:
                          signing_lane: publisher_delegated
                          resolver_source: guard_config_delegated_lane
                      ui:
                        brand:
                          name: Publisher Checkout
                          accent: "#1144aa"
                        copy:
                          title: Pay Securely
                          subtitle: Publisher delegated checkout
              schema:
                $ref: "#/components/schemas/GatewayPaymentSessionReadResponse"
        "400":
          description: Invalid request
        "404":
          description: Payment session not found

  /v1/gateway-payment/branding/logo:
    get:
      summary: Proxy a hosted branding logo from an allowlisted host
      x-compatibility-class: A
      tags: [Payments]
      parameters:
        - in: query
          name: url
          required: true
          schema: { type: string, format: uri }
      responses:
        "200":
          description: Logo image bytes
          content:
            image/*:
              schema:
                type: string
                format: binary
        "400":
          description: Missing/invalid URL
        "403":
          description: Logo host not allowlisted
        "415":
          description: Upstream response is not image content
        "502":
          description: Upstream fetch failed

  /v1/gateway-payment/complete:
    post:
      summary: Complete gateway-managed payment and return signed redirect URL
      x-compatibility-class: A
      tags: [Payments]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GatewayPaymentCompleteRequest"
      responses:
        "200":
          description: Gateway payment completion result
          content:
            application/json:
              examples:
                gateway_managed_immediate:
                  summary: Gateway-managed immediate completion
                  value:
                    status: success
                    result:
                      redirect_url: "https://host.example/return?xapps_payment_status=paid&xapps_payment_session_id=pay_gw_123&xapps_payment_sig=abc123&xapps_payment_sig_kid=gw-k1"
                      scheme:
                        scheme_key: mock_immediate
                        adapter_dispatch_key: mock_immediate
                        supported_flows: [immediate]
                        status: active
                tenant_delegated_hosted_redirect:
                  summary: Tenant-delegated hosted redirect completion
                  value:
                    status: success
                    result:
                      redirect_url: "https://host.example/return?xapps_payment_status=paid&xapps_payment_session_id=pay_td_123&xapps_payment_issuer=tenant_delegated&xapps_payment_signing_lane=tenant_delegated&xapps_payment_resolver_source=guard_config_delegated_lane&xapps_payment_sig=abc123&xapps_payment_sig_kid=tenant-k1"
                      scheme:
                        scheme_key: stripe_checkout
                        adapter_dispatch_key: stripe_checkout
                        supported_flows: [hosted_redirect]
                        status: active
                publisher_delegated_hosted_redirect:
                  summary: Publisher-delegated hosted redirect completion
                  value:
                    status: success
                    result:
                      redirect_url: "https://host.example/return?xapps_payment_status=paid&xapps_payment_session_id=pay_pd_123&xapps_payment_issuer=publisher_delegated&xapps_payment_signing_lane=publisher_delegated&xapps_payment_resolver_source=guard_config_delegated_lane&xapps_payment_sig=abc123&xapps_payment_sig_kid=publisher-k1"
                      scheme:
                        scheme_key: stripe_checkout
                        adapter_dispatch_key: stripe_checkout
                        supported_flows: [hosted_redirect]
                        status: active
                client_collect_handoff:
                  summary: Additive client-collect handoff
                  value:
                    status: success
                    result:
                      flow: client_collect
                      payment_session_id: pay_cc_123
                      client_settle_url: "https://gateway.example/v1/gateway-payment/client-settle"
                      provider_reference: cc_pay_cc_123
                      scheme:
                        scheme_key: mock_client_collect
                        adapter_dispatch_key: mock_client_collect
                        supported_flows: [client_collect]
                        status: active
                      metadata:
                        provider_flow: client_collect
              schema:
                $ref: "#/components/schemas/GatewayPaymentCompleteResponse"
        "400":
          description: Invalid request
        "404":
          description: Payment session not found
        "409":
          description: Payment session already used

  /v1/gateway-payment/provider-return:
    get:
      summary: Gateway-managed provider hosted return finalization
      x-compatibility-class: A
      tags: [Payments]
      parameters:
        - in: query
          name: payment_session_id
          required: true
          schema: { type: string }
        - in: query
          name: provider
          required: true
          schema: { type: string }
      responses:
        "302":
          description: Redirect to signed xApps payment return URL
          headers:
            Location:
              description: Signed hosted-return redirect URL.
              schema:
                type: string
                format: uri
                example: "https://host.example/return?xapps_payment_status=paid&xapps_payment_session_id=pay_td_123&xapps_payment_issuer=tenant_delegated&xapps_payment_signing_lane=tenant_delegated&xapps_payment_resolver_source=guard_config_delegated_lane&xapps_payment_sig=abc123&xapps_payment_sig_kid=tenant-k1"
        "400":
          description: Invalid request
        "404":
          description: Payment session not found
    post:
      summary: Gateway-managed provider hosted return finalization (provider POST callback)
      x-compatibility-class: A
      tags: [Payments]
      parameters:
        - in: query
          name: payment_session_id
          required: true
          schema: { type: string }
        - in: query
          name: provider
          required: true
          schema: { type: string }
      requestBody:
        required: false
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              additionalProperties: true
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        "302":
          description: Redirect to signed xApps payment return URL
          headers:
            Location:
              description: Signed hosted-return redirect URL.
              schema:
                type: string
                format: uri
        "400":
          description: Invalid request
        "404":
          description: Payment session not found

  /v1/gateway-payment/netopia/customer-action:
    get:
      summary: Netopia customer authentication handoff page
      x-compatibility-class: A
      tags: [Payments]
      parameters:
        - in: query
          name: payment_session_id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: HTML page that auto-submits the Netopia 3DS customer authentication form
          content:
            text/html:
              schema: { type: string }
        "400":
          description: Missing payment_session_id
        "404":
          description: Payment session not found
        "409":
          description: Netopia customer authentication handoff not available for this session

  /v1/gateway-payment/client-settle:
    post:
      summary: Finalize client_collect gateway payment flow and return signed redirect URL
      x-compatibility-class: A
      tags: [Payments]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GatewayPaymentClientSettleRequest"
      responses:
        "200":
          description: Gateway client-settle finalization result
          content:
            application/json:
              examples:
                tenant_delegated_client_settle_paid:
                  summary: Tenant-delegated client-settle success
                  value:
                    status: success
                    result:
                      redirect_url: "https://host.example/return?xapps_payment_status=paid&xapps_payment_session_id=pay_td_cc_123&xapps_payment_issuer=tenant_delegated&xapps_payment_signing_lane=tenant_delegated&xapps_payment_resolver_source=guard_config_delegated_lane&xapps_payment_sig=abc123&xapps_payment_sig_kid=tenant-k1"
                publisher_delegated_client_settle_failed:
                  summary: Publisher-delegated client-settle failure
                  value:
                    status: success
                    result:
                      redirect_url: "https://host.example/return?xapps_payment_status=failed&xapps_payment_session_id=pay_pd_cc_123&xapps_payment_issuer=publisher_delegated&xapps_payment_signing_lane=publisher_delegated&xapps_payment_resolver_source=guard_config_delegated_lane&xapps_payment_sig=abc123&xapps_payment_sig_kid=publisher-k1"
              schema:
                $ref: "#/components/schemas/GatewayPaymentClientSettleResponse"
        "400":
          description: Invalid request
        "404":
          description: Payment session not found
        "409":
          description: Payment session already used or invalid state

  /v1/gateway-payment/webhook/{provider}:
    post:
      summary: Receive provider webhook for payment session finality
      x-compatibility-class: A
      tags: [Payments]
      parameters:
        - in: path
          name: provider
          required: true
          schema: { type: string }
          description: Payment provider key (e.g. stripe)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: Raw webhook payload from payment provider
      responses:
        "200":
          description: Webhook accepted
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, example: ok }
                  outcome: { type: string, enum: [accepted_new, accepted_duplicate, accepted_noop] }
                  reason: { type: string }
        "400":
          description: Webhook rejected (invalid signature, unsupported provider)

  /v1/me/tenants:
    get:
      summary: List switchable tenants for current portal user session
      tags: [UserPortal]
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Tenant list
          content:
            application/json:
              schema:
                type: object
                properties:
                  tenants:
                    type: array
                    items:
                      type: object
                      properties:
                        client_id: { type: string }
                        slug: { type: string }
                        name: { type: string }
                        source: { type: string }
                        installation_policy:
                          $ref: "#/components/schemas/ClientInstallationPolicy"
        "401":
          description: Authentication required

  /v1/me/subjects:
    get:
      summary: List switchable subject identities for current portal user session
      tags: [UserPortal]
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Subject identity list
          content:
            application/json:
              schema:
                type: object
                properties:
                  active_subject_id:
                    type: string
                    nullable: true
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/SwitchableSubject"
        "401":
          description: Authentication required

  /v1/me/subjects/active:
    post:
      summary: Set active subject identity for current portal user session
      tags: [UserPortal]
      security:
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [subject_id]
              properties:
                subject_id:
                  type: string
      responses:
        "200":
          description: Active subject updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:
                    type: boolean
                  active_subject_id:
                    type: string
                    nullable: true
                  subject:
                    allOf:
                      - $ref: "#/components/schemas/SwitchableSubject"
                    nullable: true
        "400":
          description: subject_id is required
        "401":
          description: Authentication required
        "404":
          description: Subject not linked to current session

  /v1/client-self:
    get:
      summary: Read current client installation policy and metadata for client-authenticated hosts
      tags: [UserPortal]
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Client metadata
          content:
            application/json:
              schema:
                type: object
                required: [client]
                properties:
                  client:
                    type: object
                    properties:
                      id: { type: string }
                      name: { type: string }
                      slug: { type: string }
                      status: { type: string }
                      installation_policy:
                        $ref: "#/components/schemas/ClientInstallationPolicy"
        "401":
          description: Client auth required
        "404":
          description: Client not found

  /v1/tunnel/connect:
    get:
      summary: Agent tunnel WebSocket endpoint
      tags: [Tunnel]
      description: |
        WebSocket upgrade endpoint for publisher agents. Requires AGENT_TUNNEL_ENABLED=true.
        The agent authenticates with a publisher session token and connects a persistent tunnel
        for request dispatch (AGENT_TUNNEL executor mode).
      parameters:
        - name: token
          in: query
          required: true
          schema: { type: string }
          description: Publisher session PASETO token
        - name: xappVersionId
          in: query
          required: true
          schema: { type: string }
          description: Xapp version ID (must have executor_mode AGENT_TUNNEL)
        - name: agentId
          in: query
          schema: { type: string }
          description: Self-reported agent identifier
      responses:
        "101":
          description: Switching Protocols (WebSocket upgrade)
        "401":
          description: Authentication failed

  /v1/tunnel/status:
    get:
      summary: List active agent tunnels
      tags: [Tunnel]
      description: |
        Returns a list of currently connected agent tunnels.
        Publishers see only their own tunnels; superadmins see all.
      security:
        - BearerAuth: []
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  tunnels:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string }
                        publisherId: { type: string }
                        xappVersionId: { type: string }
                        agentId: { type: string }
                        connectedAt: { type: string, format: date-time }
                        lastHeartbeat: { type: string, format: date-time }

components:
  responses:
    Unauthorized:
      description: Unauthorized
      content:
        application/json:
          schema:
            type: object
            properties:
              message: { type: string }

  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: Client API key for server-to-server calls.
    SuperadminKeyAuth:
      type: apiKey
      in: header
      name: X-Superadmin-Key
      description: Superadmin API key for machine/operator administration. Browser-origin requests are rejected for this key auth path.
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: PASETO
      description: |
        PASETO token. Multiple token types are issued for different contexts:
        - `typ: "user"` — User portal session (from `/v1/auth/email/verify` or `/v1/auth/exchange`).
        - `typ: "widget"` — Widget session (from `/v1/widget-sessions`).
        - `typ: "system_user"` with `role: "publisher"` — Publisher console session (from `/v1/auth/system/verify`).
        - `typ: "publisher_widget"` — Publisher server-to-server widget token (from `/v1/publisher/widget-sessions`).
        - `typ: "publisher_callback"` — Request-scoped callback token (minted during dispatch).
        - `typ: "catalog"` — Catalog embed session (from `/v1/catalog-sessions`).
        - `typ: "system_user"` — System user session (from `/v1/auth/system/verify`) with `role` and optional `roles`.

        For `/v1/superadmin/*` operations, bearer tokens are accepted alongside `X-Superadmin-Key`.
        Route-level RBAC still applies: superadmin role has full scope, while tenant role is limited to tenant-owned resources on supported endpoints.

  schemas:
    JsonValue:
      description: Arbitrary JSON value.
      nullable: true
      oneOf:
        - type: object
          additionalProperties: true
        - type: array
          items: {}
        - type: string
        - type: number
        - type: boolean

    PortalSubjectIdentity:
      type: object
      additionalProperties: false
      properties:
        hint: { type: string, nullable: true }
        identifier_type: { type: string, nullable: true }
        detail: { type: string, nullable: true }

    SwitchableSubject:
      type: object
      additionalProperties: false
      properties:
        id: { type: string }
        subject_id: { type: string }
        type: { type: string, nullable: true }
        label: { type: string, nullable: true }
        is_primary: { type: boolean }
        active: { type: boolean }
        identity:
          allOf:
            - $ref: "#/components/schemas/PortalSubjectIdentity"
          nullable: true

    ErrorCode:
      type: string
      description: |
        Canonical API error code contract.
        Baseline normalized codes are documented here, but route/domain-specific codes are also valid
        (for example `GUARD_BLOCKED`, `NEEDS_LINKING`, `update_required`) and must be treated as
        stable machine values by clients.
      oneOf:
        - type: string
          enum:
            [
              INVALID_REQUEST,
              BAD_REQUEST,
              UNAUTHORIZED,
              FORBIDDEN,
              NOT_FOUND,
              CONFLICT,
              RATE_LIMITED,
              INTERNAL_ERROR,
            ]
        - type: string
          pattern: "^[A-Za-z0-9_:-]+$"

    ErrorObject:
      type: object
      required: [code, message, status]
      properties:
        code:
          $ref: "#/components/schemas/ErrorCode"
        message:
          type: string
        status:
          type: integer
        details:
          description: Optional validation or domain-specific error context.
          nullable: true
        request_id:
          type: string
        path:
          type: string

    ErrorResponse:
      type: object
      required: [message, code, error]
      properties:
        message:
          type: string
        code:
          $ref: "#/components/schemas/ErrorCode"
        error:
          $ref: "#/components/schemas/ErrorObject"

    PaymentPricingDetails:
      type: object
      description: Normalized pricing block emitted in payment-related guard details.
      additionalProperties: true
      properties:
        currency: { type: string, nullable: true }
        default_amount: { type: number, nullable: true }
        xapp_prices:
          type: object
          nullable: true
          additionalProperties: { type: number }
        tool_overrides:
          type: object
          nullable: true
          additionalProperties: { type: number }
        description: { type: string, nullable: true }
        asset: { type: string, nullable: true }
        pay_to: { type: string, nullable: true }

    PaymentAcceptsEntry:
      type: object
      description: One accepted payment method emitted in `details.accepts[]`.
      additionalProperties: true
      properties:
        scheme: { type: string }
        contract: { type: string, nullable: true }
        network: { type: string, nullable: true }
        currency: { type: string, nullable: true }
        amount: { type: string, nullable: true }
        asset: { type: string, nullable: true }
        pay_to: { type: string, nullable: true }
        payment_url: { type: string, nullable: true }
        label: { type: string, nullable: true }
        metadata:
          type: object
          nullable: true
          additionalProperties: true
          description: Optional presentation-safe scheme metadata surfaced to the hosted payment shell. May include hints such as `supported_flows`, `recommended`, `badge`, `title`, `subtitle`, or `description`.

    PaymentSchemeRegistryDescriptor:
      type: object
      description: |
        Additive scheme-registry metadata emitted by gateway/provider adapters.
        This is descriptive metadata only and does not change guard/evidence semantics.
      additionalProperties: true
      required: [scheme_key]
      properties:
        scheme_key: { type: string }
        adapter_dispatch_key: { type: string, nullable: true }
        supported_flows:
          type: array
          nullable: true
          items:
            type: string
            enum: [immediate, hosted_redirect, client_collect]
        status:
          type: string
          nullable: true
          enum: [active, disabled, deprecated, experimental]
        metadata:
          type: object
          nullable: true
          additionalProperties: true

    PaymentSessionCreateRequest:
      type: object
      required: [xapp_id, tool_name, amount, return_url]
      properties:
        payment_session_id: { type: string }
        page_url: { type: string, format: uri }
        xapp_id: { type: string }
        tool_name: { type: string }
        amount: { oneOf: [{ type: number }, { type: string }] }
        currency: { type: string }
        issuer: { type: string }
        scheme:
          type: string
          description: Requested settlement scheme (for scheme-to-provider routing).
        payment_scheme:
          type: string
          description: Alias of `scheme` for compatibility with guard/payment terminology.
        return_url: { type: string, format: uri }
        cancel_url: { type: string, format: uri }
        xapps_resume: { type: string }
        subject_id: { type: string }
        installation_id: { type: string }
        client_context_id: { type: string }
        metadata:
          type: object
          additionalProperties: true

    PaymentSessionCompleteRequest:
      type: object
      properties:
        xapps_resume: { type: string }
        issuer: { type: string }
        status: { type: string }
        metadata:
          type: object
          additionalProperties: true

    PaymentSessionReconcileRequest:
      type: object
      additionalProperties: false
      properties:
        status: { type: string, enum: [pending, cancelled] }
        action:
          type: string
          enum:
            [
              replay_invoice_hooks,
              refund,
              reconcile_usage_credit,
              reconcile_settlement,
              repair_usage_credit_reservation,
              review_dispute_restoration,
            ]
        note: { type: string }

    RequestPaymentReconcileRequest:
      type: object
      additionalProperties: false
      properties:
        payment_session_id:
          type: string
          description: Optional explicit session id override. If omitted, gateway resolves from request guard summary.

    RequestPaymentReconcileResponse:
      type: object
      required: [status, payment_session_id, provider_status, finality_state, reconciled]
      properties:
        status:
          type: string
          enum: [ok]
        payment_session_id:
          type: string
        provider_status:
          type: string
          enum: [paid, failed, cancelled, pending]
        finality_state:
          type: string
          enum: [confirmed_paid, still_pending, failed]
        reconciled:
          type: boolean
        redirect_url:
          type: string
          nullable: true

    GatewayPaymentCompleteRequest:
      type: object
      required: [payment_session_id]
      properties:
        payment_session_id: { type: string }
        scheme:
          type: string
          description: Optional payment scheme override selected by hosted UI (`accepts[]` negotiation).
        payment_scheme:
          type: string
          description: Alias of `scheme` for compatibility with payment policy payloads.
        return_url: { type: string, format: uri }
        cancel_url: { type: string, format: uri }
        xapps_resume: { type: string }

    GatewayPaymentClientSettleRequest:
      type: object
      required: [payment_session_id]
      properties:
        payment_session_id: { type: string }
        client_token: { type: string }
        status:
          type: string
          enum: [paid, failed, cancelled]
        outcome:
          type: string
          enum: [paid, failed, cancelled]
        return_url: { type: string, format: uri }
        xapps_resume: { type: string }

    PaymentSessionObject:
      type: object
      description: Gateway payment session record (additive, provider/profile-safe).
      additionalProperties: true
      properties:
        invoice_attention:
          allOf:
            - $ref: "#/components/schemas/PaymentInvoiceAttentionSummary"
          nullable: true
        settlement_effect:
          type: string
          nullable: true
          enum: [payment_refunded, payment_chargeback]
        settlement_effect_detail: { type: string, nullable: true }
        dispute_resolution:
          type: object
          nullable: true
          additionalProperties: false
          properties:
            status: { type: string, nullable: true }
            source:
              type: string
              enum: [provider_webhook]
            recorded_at:
              type: string
              format: date-time
              nullable: true
            operator_follow_up_required: { type: boolean }
            operator_follow_up_kind:
              type: string
              nullable: true
              enum: [review_restoration]
            restoration_candidate: { type: boolean }
            operator_follow_up_reviewed_at:
              type: string
              format: date-time
              nullable: true
            operator_follow_up_reviewed_by:
              type: object
              nullable: true
              additionalProperties: false
              properties:
                actor_type:
                  type: string
                  enum: [superadmin, publisher]
                actor_id: { type: string }
            operator_follow_up_note: { type: string, nullable: true }
        restoration_review_summary:
          type: object
          nullable: true
          additionalProperties: false
          properties:
            subscription_contracts_expired: { type: integer }
            entitlements_expired: { type: integer }
            wallet_accounts_adjusted: { type: integer }
            wallet_refund_entries: { type: integer }
            wallet_refund_amount: { type: string, nullable: true }
            wallet_refund_currency: { type: string, nullable: true }
            access_snapshots_refreshed: { type: integer }
        usage_credit:
          allOf:
            - $ref: "#/components/schemas/PaymentUsageCreditSummary"
          nullable: true
        usage_credit_history:
          type: array
          nullable: true
          items:
            $ref: "#/components/schemas/PaymentUsageCreditHistoryEntry"
        usage_credit_repair_eligible:
          type: boolean
          description:
            True only when this payment session belongs to the pay-by-request usage-credit lane and
            can participate in usage-credit backfill or stale-reservation repair flows.
        usage_credit_repair_attention:
          type: string
          nullable: true
          enum: [missing_credit, stale_reservation]
          description:
            Focused operator repair hint for pay-by-request usage-credit sessions. XMS payment
            sessions must return null here and are not part of usage-credit repair flows.
        accepts:
          type: array
          nullable: true
          description: Canonical payment method negotiation surface (`accepts[]`).
          items:
            $ref: "#/components/schemas/PaymentAcceptsEntry"
        ui:
          $ref: "#/components/schemas/HostedPaymentUiContract"

    PaymentUsageCreditSummary:
      type: object
      additionalProperties: false
      required: [state]
      properties:
        state:
          type: string
          enum: [available, reserved, consumed]
        request_id: { type: string, nullable: true }
        guard_slug: { type: string, nullable: true }
        subject_id: { type: string, nullable: true }
        installation_id: { type: string, nullable: true }
        xapp_id: { type: string, nullable: true }
        tool_name: { type: string, nullable: true }
        amount: { type: string, nullable: true }
        currency: { type: string, nullable: true }
        consumed_at:
          type: string
          format: date-time
          nullable: true
        updated_at:
          type: string
          format: date-time
          nullable: true
        last_seen_at:
          type: string
          format: date-time
          nullable: true

    RequestOpsAttention:
      type: object
      additionalProperties: false
      properties:
        invoice:
          allOf:
            - $ref: "#/components/schemas/RequestInvoiceAttention"
          nullable: true
        notification:
          allOf:
            - $ref: "#/components/schemas/RequestNotificationAttention"
          nullable: true

    RequestInvoiceAttention:
      type: object
      additionalProperties: false
      required: [state, severity, replayable, message]
      properties:
        state:
          type: string
          enum: [failed]
        severity:
          type: string
          enum: [critical]
        replayable:
          type: boolean
        message: { type: string }
        reason: { type: string, nullable: true }
        trigger: { type: string, nullable: true }
        lastEventType: { type: string, nullable: true }
        occurredAt:
          type: string
          format: date-time
          nullable: true

    RequestNotificationAttention:
      type: object
      additionalProperties: false
      required: [state, severity, replayable, message]
      properties:
        state:
          type: string
          enum: [failed, retry_scheduled]
        severity:
          type: string
          enum: [critical, warning]
        replayable:
          type: boolean
        message: { type: string }
        reason: { type: string, nullable: true }
        trigger: { type: string, nullable: true }
        lastEventType: { type: string, nullable: true }
        retryAttemptCount: { type: integer, nullable: true }
        occurredAt:
          type: string
          format: date-time
          nullable: true

    PaymentUsageCreditHistoryEntry:
      type: object
      additionalProperties: false
      required: [type, label, at]
      properties:
        type:
          type: string
          enum:
            [
              payment_completed,
              payment_refunded,
              payment_chargeback,
              usage_credit_created,
              usage_credit_reserved,
              usage_credit_consumed,
              usage_credit_reserved_stale,
            ]
        label: { type: string }
        at:
          type: string
          format: date-time
        state:
          type: string
          enum: [available, reserved, consumed]
          nullable: true
        request_id: { type: string, nullable: true }
        request_status: { type: string, nullable: true }
        guard_slug: { type: string, nullable: true }

    PaymentInvoiceAttentionSummary:
      type: object
      additionalProperties: false
      required: [state, severity, replayable, message]
      properties:
        state:
          type: string
          enum: [failed, retry_scheduled]
        severity:
          type: string
          enum: [critical, warning]
        replayable:
          type: boolean
        message:
          type: string
        reason:
          type: string
          nullable: true
        retryAttemptCount:
          type: integer
          nullable: true
        lastEventType:
          type: string
          nullable: true

    HostedPaymentUiContract:
      type: object
      description: Presentation-only hosted payment UI contract (non-authoritative for settlement/signing logic).
      additionalProperties: false
      properties:
        brand:
          type: object
          additionalProperties: false
          properties:
            name: { type: string, nullable: true }
            logo_url: { type: string, format: uri, nullable: true }
            accent: { type: string, nullable: true }
            mode: { type: string, nullable: true }
        copy:
          type: object
          additionalProperties: false
          properties:
            title: { type: string, nullable: true }
            subtitle: { type: string, nullable: true }
        schemes:
          type: array
          nullable: true
          description: Optional scheme-aware presentation overrides applied by the hosted payment shell after method selection. These affect copy, accent, and UI framing only; settlement authority remains server-side.
          items:
            $ref: "#/components/schemas/HostedPaymentSchemeUiContract"

    HostedPaymentSchemeUiContract:
      type: object
      additionalProperties: false
      required: [scheme]
      properties:
        scheme:
          type: string
          description: Payment scheme key this presentation override targets.
        title:
          type: string
          nullable: true
        subtitle:
          type: string
          nullable: true
        badge:
          type: string
          nullable: true
        cta_label:
          type: string
          nullable: true
        help_text:
          type: string
          nullable: true
        accent:
          type: string
          nullable: true
        mode:
          type: string
          nullable: true
          description: Hosted payment shell presentation mode hint, currently `light` or `dark`.

    ProviderCredentialRefsContract:
      type: object
      additionalProperties:
        type: object
        additionalProperties: { type: string }

    InvoiceProviderRefsContract:
      allOf:
        - $ref: "#/components/schemas/ProviderCredentialRefsContract"
      description: |
        Owner-scoped invoice provider refs. The runtime preserves both
        `invoice_provider_credentials_refs` and `invoice_provider_secret_refs` for parity with
        payments and notifications, but the first delegated invoice UI intentionally exposes a
        simplified provider-bundle surface for the `custom_template` + SmartBill baseline.

    NotificationUiContract:
      type: object
      additionalProperties: false
      properties:
        email:
          type: object
          additionalProperties: false
          properties:
            from_email: { type: string, nullable: true }
            from_name: { type: string, nullable: true }
            provider_key: { type: string, nullable: true }

    NotificationTemplateContract:
      type: object
      additionalProperties: false
      properties:
        subject: { type: string, nullable: true }
        text: { type: string, nullable: true }
        html: { type: string, nullable: true }

    NotificationTemplateOverridesContract:
      type: object
      additionalProperties: false
      properties:
        templates:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/NotificationTemplateContract"

    InvoiceUiContract:
      type: object
      additionalProperties: false
      description: |
        First delegated invoice owner-settings surface. Provider selection is explicit here,
        while provider bundle refs and template overrides live in sibling fields. Issuer profile
        fields capture the owner-authored invoice identity used as authoritative defaults by the
        invoice runtime. Country tax values are configured centrally under `tax_policy`, while
        tenant/publisher/gateway issuer settings indicate `country_code` and VAT-payer status.
      properties:
        provider_key: { type: string, nullable: true }
        issuer:
          type: object
          additionalProperties: false
          properties:
            name: { type: string, nullable: true }
            company_identification_number: { type: string, nullable: true }
            company_registration_number: { type: string, nullable: true }
            vat_code: { type: string, nullable: true }
            address: { type: string, nullable: true }
            city: { type: string, nullable: true }
            county: { type: string, nullable: true }
            country: { type: string, nullable: true }
            country_code: { type: string, nullable: true }
            is_tax_payer: { type: boolean, nullable: true }
            email: { type: string, nullable: true }
            phone: { type: string, nullable: true }
            bank: { type: string, nullable: true }
            iban: { type: string, nullable: true }
        tax_policy:
          type: object
          additionalProperties: false
          properties:
            default_country_code: { type: string, nullable: true }
            countries:
              type: object
              additionalProperties:
                type: object
                additionalProperties: false
                properties:
                  standard_rate: { type: number, nullable: true }
                  tax_name: { type: string, nullable: true }
                  is_tax_included: { type: boolean, nullable: true }

    InvoiceTemplateContract:
      type: object
      additionalProperties: false
      properties:
        title: { type: string, nullable: true }
        text: { type: string, nullable: true }
        html: { type: string, nullable: true }

    InvoiceTemplateOverridesContract:
      type: object
      additionalProperties: false
      properties:
        templates:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/InvoiceTemplateContract"

    OperationalSurfaceDescriptor:
      type: object
      additionalProperties: false
      properties:
        enabled: { type: boolean }
        scope:
          type: string
          enum: [xapp]
        visibility:
          type: object
          additionalProperties: false
          properties:
            portal: { type: boolean }
            embed: { type: boolean }
        filters:
          type: object
          additionalProperties: false
          properties:
            xapp_id: { type: string }
            installation_id: { type: string, nullable: true }
        derived_from:
          type: array
          items: { type: string }

    OperationalSurfacesDescriptor:
      type: object
      additionalProperties: false
      properties:
        requests:
          $ref: "#/components/schemas/OperationalSurfaceDescriptor"
        payments:
          $ref: "#/components/schemas/OperationalSurfaceDescriptor"
        invoices:
          $ref: "#/components/schemas/OperationalSurfaceDescriptor"
        notifications:
          $ref: "#/components/schemas/OperationalSurfaceDescriptor"

    PortalInvoiceSummary:
      type: object
      additionalProperties: false
      properties:
        id: { type: string }
        request_id: { type: string, nullable: true }
        payment_session_id: { type: string, nullable: true }
        provider_key: { type: string, nullable: true }
        owner_scope: { type: string, nullable: true }
        owner_scope_id: { type: string, nullable: true }
        status: { type: string, nullable: true }
        invoice_identifier: { type: string, nullable: true }
        series: { type: string, nullable: true }
        number: { type: string, nullable: true }
        document_filename: { type: string, nullable: true }
        document_mime_type: { type: string, nullable: true }
        external_url: { type: string, nullable: true }
        lifecycle_status: { type: string, nullable: true }
        lifecycle_parent_invoice_id: { type: string, nullable: true }
        lifecycle_reason: { type: string, nullable: true }
        lifecycle_updated_at: { type: string, nullable: true }
        created_at: { type: string, nullable: true }

    InvoiceLifecycleEventSummary:
      type: object
      additionalProperties: false
      properties:
        id: { type: string }
        type: { type: string }
        occurred_at: { type: string, nullable: true }
        action: { type: string, nullable: true }
        reason: { type: string, nullable: true }
        actor_scope: { type: string, nullable: true }
        actor_id: { type: string, nullable: true }
        invoice_record_id: { type: string, nullable: true }
        invoice_identifier: { type: string, nullable: true }
        lifecycle_status: { type: string, nullable: true }
        parent_invoice_record_id: { type: string, nullable: true }
        parent_invoice_identifier: { type: string, nullable: true }
        reversal_invoice_record_id: { type: string, nullable: true }
        reversal_invoice_identifier: { type: string, nullable: true }
        provider_result:
          type: object
          nullable: true
          additionalProperties: true

    InvoiceHistoryResponse:
      type: object
      additionalProperties: false
      properties:
        item:
          allOf:
            - $ref: "#/components/schemas/PortalInvoiceSummary"
          nullable: true
        root_invoice_id: { type: string, nullable: true }
        lineage:
          type: array
          items:
            $ref: "#/components/schemas/PortalInvoiceSummary"
        events:
          type: array
          items:
            $ref: "#/components/schemas/InvoiceLifecycleEventSummary"
        document_url: { type: string, nullable: true }
        document_download_url: { type: string, nullable: true }

    PortalPaymentSessionSummary:
      type: object
      additionalProperties: false
      properties:
        id: { type: string }
        client_id: { type: string }
        payment_session_id: { type: string }
        xapp_id: { type: string }
        xapp_name: { type: string, nullable: true }
        tool_name: { type: string }
        amount: { type: string }
        currency: { type: string }
        issuer: { type: string }
        provider_key: { type: string }
        status: { type: string }
        return_url: { type: string }
        cancel_url: { type: string, nullable: true }
        xapps_resume: { type: string, nullable: true }
        has_xapps_resume: { type: boolean, nullable: true }
        subject_id: { type: string, nullable: true }
        installation_id: { type: string, nullable: true }
        page_url: { type: string, nullable: true }
        completed_at: { type: string, nullable: true }
        created_at: { type: string, nullable: true }
        updated_at: { type: string, nullable: true }
        expires_at: { type: string, nullable: true }
        request_id: { type: string, nullable: true }
        settlement_effect:
          type: string
          nullable: true
          enum: [payment_refunded, payment_chargeback]
        settlement_effect_detail: { type: string, nullable: true }
        dispute_resolution:
          type: object
          nullable: true
          additionalProperties: false
          properties:
            status: { type: string, nullable: true }
            source:
              type: string
              enum: [provider_webhook]
            recorded_at:
              type: string
              format: date-time
              nullable: true
            operator_follow_up_required: { type: boolean }
            operator_follow_up_kind:
              type: string
              nullable: true
              enum: [review_restoration]
            restoration_candidate: { type: boolean }
            operator_follow_up_reviewed_at:
              type: string
              format: date-time
              nullable: true
            operator_follow_up_reviewed_by:
              type: object
              nullable: true
              additionalProperties: false
              properties:
                actor_type:
                  type: string
                  enum: [superadmin, publisher]
                actor_id: { type: string }
            operator_follow_up_note: { type: string, nullable: true }
        restoration_review_summary:
          type: object
          nullable: true
          additionalProperties: false
          properties:
            subscription_contracts_expired: { type: integer }
            entitlements_expired: { type: integer }
            wallet_accounts_adjusted: { type: integer }
            wallet_refund_entries: { type: integer }
            wallet_refund_amount: { type: string, nullable: true }
            wallet_refund_currency: { type: string, nullable: true }
            access_snapshots_refreshed: { type: integer }
        usage_credit:
          allOf:
            - $ref: "#/components/schemas/PaymentUsageCreditSummary"
          nullable: true
        usage_credit_history:
          type: array
          nullable: true
          items:
            $ref: "#/components/schemas/PaymentUsageCreditHistoryEntry"
        invoices:
          type: array
          nullable: true
          items:
            $ref: "#/components/schemas/PortalInvoiceSummary"

    PortalNotificationCategoryPreferences:
      type: object
      additionalProperties: false
      properties:
        request_created: { type: boolean }
        response_finalized: { type: boolean }
        payment_completed: { type: boolean }

    PortalNotificationPreferences:
      type: object
      additionalProperties: false
      properties:
        email_enabled: { type: boolean }
        portal_inbox_enabled: { type: boolean }
        categories:
          $ref: "#/components/schemas/PortalNotificationCategoryPreferences"

    PortalPreferences:
      type: object
      additionalProperties: false
      properties:
        locale:
          type: string
          nullable: true
          enum: [en, ro]

    PortalNotificationRecordSummary:
      type: object
      additionalProperties: false
      properties:
        id: { type: string }
        request_id: { type: string, nullable: true }
        payment_session_id: { type: string, nullable: true }
        trigger: { type: string, nullable: true }
        channel: { type: string, nullable: true }
        provider_key: { type: string, nullable: true }
        execution_mode: { type: string, nullable: true }
        owner_scope: { type: string, nullable: true }
        status: { type: string, nullable: true }
        target_to_masked: { type: string, nullable: true }
        template_name: { type: string, nullable: true }
        definition_name: { type: string, nullable: true }
        message: { type: string, nullable: true }
        reason: { type: string, nullable: true }
        retry_attempt_count: { type: integer, nullable: true }
        message_id: { type: string, nullable: true }
        unread: { type: boolean }
        read_at: { type: string, nullable: true }
        created_at: { type: string, nullable: true }

    PortalSubjectProfileOrigin:
      type: object
      additionalProperties: false
      properties:
        source_label: { type: string, nullable: true }
        detail: { type: string, nullable: true }
        metadata:
          type: object
          nullable: true
          additionalProperties: true

    PortalSubjectProfileSummaryFields:
      type: object
      additionalProperties: false
      properties:
        name: { type: string, nullable: true }
        company_name: { type: string, nullable: true }
        email: { type: string, nullable: true }
        company_identification_number: { type: string, nullable: true }
        company_registration_number: { type: string, nullable: true }
        vat_code: { type: string, nullable: true }
        phone: { type: string, nullable: true }
        address: { type: string, nullable: true }
        city: { type: string, nullable: true }
        country: { type: string, nullable: true }

    PortalSubjectProfileSummary:
      type: object
      additionalProperties: false
      properties:
        subject_id: { type: string }
        subject_type: { type: string, nullable: true }
        storage_mode: { type: string, nullable: true }
        candidate_id: { type: string, nullable: true }
        label: { type: string, nullable: true }
        profile_family: { type: string, nullable: true }
        source: { type: string, nullable: true }
        origin:
          $ref: "#/components/schemas/PortalSubjectProfileOrigin"
        selected: { type: boolean, nullable: true }
        is_default: { type: boolean, nullable: true }
        summary:
          $ref: "#/components/schemas/PortalSubjectProfileSummaryFields"
        profile:
          type: object
          nullable: true
          additionalProperties: true

    PaymentSessionEnvelope:
      type: object
      required: [session]
      properties:
        session:
          $ref: "#/components/schemas/PaymentSessionObject"

    PaymentSessionListResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/PaymentSessionObject"

    SettlementCurrencyAmount:
      type: object
      required: [currency, amount]
      properties:
        currency: { type: string }
        amount: { type: string }

    SettlementOverviewAccount:
      type: object
      required: [account_id, owner_kind, owner_ref, currency, balance, entry_count]
      properties:
        account_id: { type: string }
        owner_kind:
          type: string
          enum: [gateway, tenant, publisher]
        owner_ref: { type: string }
        currency: { type: string }
        balance: { type: string }
        entry_count: { type: integer }

    SettlementOverviewSourceEvent:
      type: object
      required:
        [
          id,
          occurred_at,
          currency,
          gross_amount,
          settlement_basis_amount,
          lane,
          receiver_of_funds_kind,
          receiver_of_funds_ref,
          booking_status,
          client_id,
        ]
      properties:
        id: { type: string }
        occurred_at: { type: string, format: date-time }
        currency: { type: string }
        gross_amount: { type: string }
        settlement_basis_amount: { type: string }
        lane: { type: string }
        receiver_of_funds_kind:
          type: string
          enum: [gateway, tenant, publisher]
        receiver_of_funds_ref: { type: string }
        booking_status: { type: string }
        payment_session_id: { type: string, nullable: true }
        receipt_id: { type: string, nullable: true }
        client_id: { type: string }
        publisher_id: { type: string, nullable: true }
        xapp_id: { type: string, nullable: true }
        resolved_split_policy_id: { type: string, nullable: true }
        split_policy_snapshot_ref: { type: string, nullable: true }
        effective_split_policy_source: { type: string, nullable: true }
        effective_split_scope_kind: { type: string, nullable: true }
        effective_split_scope_ref: { type: string, nullable: true }
        effective_split_currency: { type: string, nullable: true }
        effective_split_version: { type: string, nullable: true }
        effective_split_label: { type: string, nullable: true }

    SettlementOverviewResponse:
      type: object
      required:
        [
          source_event_count,
          booked_source_event_count,
          gross_by_currency,
          accounts,
          recent_source_events,
          analytics,
        ]
      properties:
        source_event_count: { type: integer }
        booked_source_event_count: { type: integer }
        gross_by_currency:
          type: array
          items:
            $ref: "#/components/schemas/SettlementCurrencyAmount"
        accounts:
          type: array
          items:
            $ref: "#/components/schemas/SettlementOverviewAccount"
        recent_source_events:
          type: array
          items:
            $ref: "#/components/schemas/SettlementOverviewSourceEvent"
        analytics:
          type: object
          required:
            [
              source_event_progress,
              outstanding_aging_by_currency,
              throughput_trend,
              closure_rates,
              payout_velocity,
            ]
          properties:
            source_event_progress:
              type: object
              properties:
                open: { type: integer }
                partial: { type: integer }
                settled: { type: integer }
                retained: { type: integer }
            outstanding_aging_by_currency:
              type: array
              items:
                type: object
                properties:
                  currency: { type: string }
                  current_amount: { type: string }
                  days_8_to_30_amount: { type: string }
                  days_31_plus_amount: { type: string }
                  source_event_count: { type: integer }
            throughput_trend:
              type: array
              items:
                type: object
                properties:
                  window_days: { type: integer, enum: [7, 30] }
                  current_source_event_count: { type: integer }
                  previous_source_event_count: { type: integer }
                  current_gross_by_currency:
                    type: array
                    items:
                      $ref: "#/components/schemas/SettlementCurrencyAmount"
                  previous_gross_by_currency:
                    type: array
                    items:
                      $ref: "#/components/schemas/SettlementCurrencyAmount"
            closure_rates:
              type: array
              items:
                type: object
                properties:
                  window_days: { type: integer, enum: [7, 30] }
                  total_source_events: { type: integer }
                  closed_source_events: { type: integer }
                  closed_rate_percent: { type: string }
            payout_velocity:
              type: object
              properties:
                window_days: { type: integer, enum: [30] }
                paid_batch_count: { type: integer }
                average_close_days: { type: number, nullable: true }
                executed_amount_by_currency:
                  type: array
                  items:
                    $ref: "#/components/schemas/SettlementCurrencyAmount"

    SettlementAllocationLedgerItem:
      type: object
      required:
        [
          id,
          settlement_source_event_id,
          settlement_account_id,
          account_owner_kind,
          account_owner_ref,
          counterparty_kind,
          direction,
          amount,
          currency,
          entry_kind,
          source_kind,
          source_ref,
          occurred_at,
          metadata_jsonb,
        ]
      properties:
        id: { type: string }
        settlement_source_event_id: { type: string }
        settlement_account_id: { type: string }
        account_owner_kind:
          type: string
          enum: [gateway, tenant, publisher]
        account_owner_ref: { type: string }
        counterparty_kind:
          type: string
          enum: [gateway, tenant, publisher]
        counterparty_ref: { type: string, nullable: true }
        direction: { type: string }
        amount: { type: string }
        currency: { type: string }
        entry_kind: { type: string }
        source_kind: { type: string }
        source_ref: { type: string }
        request_id: { type: string, nullable: true }
        payment_session_id: { type: string, nullable: true }
        purchase_intent_id: { type: string, nullable: true }
        purchase_transaction_id: { type: string, nullable: true }
        invoice_id: { type: string, nullable: true }
        split_policy_snapshot_ref: { type: string, nullable: true }
        settlement_period_id: { type: string, nullable: true }
        occurred_at: { type: string, format: date-time }
        metadata_jsonb:
          type: object
          nullable: true
          additionalProperties: true

    SettlementAccount:
      allOf:
        - $ref: "#/components/schemas/SettlementOverviewAccount"
        - type: object
          required: [status, metadata_jsonb, created_at, updated_at]
          properties:
            status: { type: string }
            metadata_jsonb:
              type: object
              nullable: true
              additionalProperties: true
            created_at: { type: string, format: date-time }
            updated_at: { type: string, format: date-time }

    SettlementAccountDetail:
      allOf:
        - $ref: "#/components/schemas/SettlementAccount"
        - type: object
          required: [recent_entries]
          properties:
            recent_entries:
              type: array
              items:
                $ref: "#/components/schemas/SettlementAllocationLedgerItem"

    SettlementSourceEventDetail:
      allOf:
        - $ref: "#/components/schemas/SettlementOverviewSourceEvent"
        - type: object
          required:
            [
              source_family,
              source_ref,
              issuer,
              contract,
              settlement_basis_kind,
              funds_availability_status,
              split_policy_snapshot_jsonb,
              payload_jsonb,
              allocations,
            ]
          properties:
            source_family: { type: string }
            source_ref: { type: string }
            provider_event_ref: { type: string, nullable: true }
            issuer: { type: string }
            contract: { type: string }
            settlement_basis_kind: { type: string }
            discount_amount: { type: string, nullable: true }
            tax_amount: { type: string, nullable: true }
            provider_fee_amount: { type: string, nullable: true }
            net_cash_amount: { type: string, nullable: true }
            funds_availability_status: { type: string }
            funds_available_at: { type: string, format: date-time, nullable: true }
            resolved_split_policy_id: { type: string, nullable: true }
            split_policy_snapshot_jsonb:
              type: object
              nullable: true
              additionalProperties: true
            payload_jsonb:
              type: object
              nullable: true
              additionalProperties: true
            allocations:
              type: array
              items:
                $ref: "#/components/schemas/SettlementAllocationLedgerItem"

    SettlementSourceEventListResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/SettlementOverviewSourceEvent"

    SettlementSourceEventDetailEnvelope:
      type: object
      required: [item]
      properties:
        item:
          $ref: "#/components/schemas/SettlementSourceEventDetail"

    SettlementAccountListResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/SettlementAccount"

    SettlementAccountDetailEnvelope:
      type: object
      required: [item]
      properties:
        item:
          $ref: "#/components/schemas/SettlementAccountDetail"

    SettlementAllocationLedgerListResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/SettlementAllocationLedgerItem"

    SettlementOpenObligationSummaryItem:
      type: object
      required:
        [
          owner_kind,
          owner_ref,
          counterparty_kind,
          currency,
          direction,
          allocated_amount,
          settled_amount,
          outstanding_amount,
        ]
      properties:
        owner_kind:
          type: string
          enum: [gateway, tenant, publisher]
        owner_ref: { type: string }
        counterparty_kind:
          type: string
          enum: [gateway, tenant, publisher]
        counterparty_ref: { type: string, nullable: true }
        currency: { type: string }
        direction:
          type: string
          enum: [in, out]
        allocated_amount: { type: string }
        settled_amount: { type: string }
        outstanding_amount: { type: string }

    SettlementOpenObligationListResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/SettlementOpenObligationSummaryItem"

    SettlementEligiblePayoutAllocationItem:
      allOf:
        - $ref: "#/components/schemas/SettlementPayoutBatchItem"
        - type: object
          required:
            [
              settlement_account_id,
              account_owner_kind,
              account_owner_ref,
              counterparty_kind,
              settlement_source_event_id,
            ]
          properties:
            settlement_account_id: { type: string }
            account_owner_kind:
              type: string
              enum: [gateway, tenant, publisher]
            account_owner_ref: { type: string }
            counterparty_kind:
              type: string
              enum: [gateway, tenant, publisher]
            counterparty_ref: { type: string, nullable: true }
            settlement_source_event_id: { type: string }
            source_event_lane: { type: string, nullable: true }
            source_event_client_id: { type: string, nullable: true }
            source_event_publisher_id: { type: string, nullable: true }
            source_event_xapp_id: { type: string, nullable: true }
            source_event_receiver_kind:
              type: string
              enum: [gateway, tenant, publisher]
              nullable: true
            source_event_receiver_ref: { type: string, nullable: true }
            source_event_split_label: { type: string, nullable: true }
            source_event_split_policy_id: { type: string, nullable: true }

    SettlementEligiblePayoutAllocationListResponse:
      type: object
      required: [items, total_amount, item_count]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/SettlementEligiblePayoutAllocationItem"
        total_amount: { type: string }
        item_count: { type: integer }

    SettlementPayoutPeriod:
      type: object
      required:
        [
          id,
          owner_kind,
          owner_ref,
          currency,
          period_start,
          period_end,
          status,
          opened_at,
          metadata_jsonb,
          allocation_out_total,
          paid_out_total,
          outstanding_total,
          draft_batch_count,
          submitted_batch_count,
          paid_batch_count,
          can_close,
        ]
      properties:
        id: { type: string }
        owner_kind:
          type: string
          enum: [gateway, tenant, publisher]
        owner_ref: { type: string }
        currency: { type: string }
        period_start: { type: string, format: date-time }
        period_end: { type: string, format: date-time }
        status: { type: string }
        opened_at: { type: string, format: date-time }
        closed_at: { type: string, format: date-time, nullable: true }
        metadata_jsonb:
          type: object
          nullable: true
          additionalProperties: true
        allocation_out_total: { type: string }
        paid_out_total: { type: string }
        outstanding_total: { type: string }
        draft_batch_count: { type: integer }
        submitted_batch_count: { type: integer }
        paid_batch_count: { type: integer }
        can_close: { type: boolean }

    SettlementPayoutPeriodListResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/SettlementPayoutPeriod"

    SettlementPayoutPeriodEnvelope:
      type: object
      required: [item]
      properties:
        item:
          $ref: "#/components/schemas/SettlementPayoutPeriod"

    SettlementPayoutBatch:
      type: object
      required:
        [
          id,
          payer_kind,
          payer_ref,
          payee_kind,
          payee_ref,
          currency,
          status,
          total_amount,
          proof_status,
          created_at,
          updated_at,
          item_count,
          period_refs,
        ]
      properties:
        id: { type: string }
        payer_kind:
          type: string
          enum: [gateway, tenant, publisher]
        payer_ref: { type: string }
        payee_kind:
          type: string
          enum: [gateway, tenant, publisher]
        payee_ref: { type: string }
        currency: { type: string }
        status:
          type: string
          enum: [draft, submitted, paid, rejected, cancelled]
        total_amount: { type: string }
        proof_status:
          type: string
          enum: [missing, uploaded, accepted, rejected]
        proof_review:
          allOf:
            - $ref: "#/components/schemas/SettlementPayoutProofReview"
          nullable: true
        payer_confirmation:
          $ref: "#/components/schemas/SettlementPayoutPartyApproval"
        payee_confirmation:
          $ref: "#/components/schemas/SettlementPayoutPartyApproval"
        proof_ref: { type: string, nullable: true }
        proof_note: { type: string, nullable: true }
        submitted_at: { type: string, format: date-time, nullable: true }
        executed_at: { type: string, format: date-time, nullable: true }
        reviewed_at: { type: string, format: date-time, nullable: true }
        reviewed_by: { type: string, nullable: true }
        created_by: { type: string, nullable: true }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        item_count: { type: integer }
        period_refs:
          type: array
          items: { type: string }

    SettlementPayoutBatchItem:
      type: object
      required:
        [
          id,
          payout_batch_id,
          allocation_entry_id,
          settled_amount,
          currency,
          status,
          allocation_direction,
          allocation_entry_kind,
          allocation_occurred_at,
          source_ref,
        ]
      properties:
        id: { type: string }
        payout_batch_id: { type: string }
        allocation_entry_id: { type: string }
        settled_amount: { type: string }
        currency: { type: string }
        status: { type: string }
        note: { type: string, nullable: true }
        allocation_direction: { type: string }
        allocation_entry_kind: { type: string }
        allocation_occurred_at: { type: string, format: date-time }
        payment_session_id: { type: string, nullable: true }
        source_ref: { type: string }

    SettlementPayoutProof:
      type: object
      required: [id, payout_batch_id, proof_kind, uploaded_at]
      properties:
        id: { type: string }
        payout_batch_id: { type: string }
        proof_kind: { type: string }
        document_url: { type: string, nullable: true }
        document_hash: { type: string, nullable: true }
        reference_number: { type: string, nullable: true }
        note: { type: string, nullable: true }
        uploaded_by: { type: string, nullable: true }
        uploaded_at: { type: string, format: date-time }

    SettlementPayoutProofReview:
      type: object
      required: [status, reviewed_at, reviewed_by, note]
      properties:
        status:
          type: string
          enum: [missing, uploaded, accepted, rejected]
        reviewed_at: { type: string, format: date-time, nullable: true }
        reviewed_by: { type: string, nullable: true }
        note: { type: string, nullable: true }

    SettlementPayoutPartyApproval:
      type: object
      required: [status, reviewed_at, reviewed_by, note]
      properties:
        status:
          type: string
          enum: [pending, accepted, rejected]
        reviewed_at: { type: string, format: date-time, nullable: true }
        reviewed_by: { type: string, nullable: true }
        note: { type: string, nullable: true }

    SettlementPayoutExecutionEntry:
      type: object
      required:
        [
          id,
          settlement_account_id,
          counterparty_kind,
          direction,
          amount,
          currency,
          entry_kind,
          source_kind,
          source_ref,
          occurred_at,
        ]
      properties:
        id: { type: string }
        settlement_account_id: { type: string }
        counterparty_kind:
          type: string
          enum: [gateway, tenant, publisher]
        counterparty_ref: { type: string, nullable: true }
        direction: { type: string }
        amount: { type: string }
        currency: { type: string }
        entry_kind: { type: string }
        source_kind: { type: string }
        source_ref: { type: string }
        settlement_period_id: { type: string, nullable: true }
        payout_batch_id: { type: string, nullable: true }
        proof_ref: { type: string, nullable: true }
        occurred_at: { type: string, format: date-time }
        metadata_jsonb:
          type: object
          nullable: true
          additionalProperties: true

    SettlementPayoutBatchDetail:
      allOf:
        - $ref: "#/components/schemas/SettlementPayoutBatch"
        - type: object
          required:
            [
              payer_account_id,
              payee_account_id,
              metadata_jsonb,
              period_details,
              items,
              proofs,
              execution_entries,
              resolved_manual_payout_defaults,
            ]
          properties:
            payer_account_id: { type: string }
            payee_account_id: { type: string }
            metadata_jsonb:
              type: object
              nullable: true
              additionalProperties: true
            period_details:
              type: array
              items:
                $ref: "#/components/schemas/SettlementPayoutPeriod"
            items:
              type: array
              items:
                $ref: "#/components/schemas/SettlementPayoutBatchItem"
            proofs:
              type: array
              items:
                $ref: "#/components/schemas/SettlementPayoutProof"
            execution_entries:
              type: array
              items:
                $ref: "#/components/schemas/SettlementPayoutExecutionEntry"
            resolved_manual_payout_defaults:
              allOf:
                - $ref: "#/components/schemas/SettlementResolvedManualPayoutDefaults"
              nullable: true

    SettlementResolvedManualPayoutDefaults:
      type: object
      required: [minimum_amount, policy_id, scope_kind, scope_ref]
      properties:
        minimum_amount: { type: string, nullable: true }
        policy_id: { type: string, nullable: true }
        scope_kind: { type: string }
        scope_ref: { type: string, nullable: true }

    SettlementPayoutBatchListResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/SettlementPayoutBatch"

    SettlementPayoutBatchDetailEnvelope:
      type: object
      required: [item]
      properties:
        item:
          $ref: "#/components/schemas/SettlementPayoutBatchDetail"

    SettlementPayoutBatchCreateInput:
      type: object
      required: [payee_kind, currency]
      properties:
        payer_kind:
          type: string
          enum: [gateway, tenant, publisher]
          nullable: true
        payer_ref: { type: string, nullable: true }
        payee_kind:
          type: string
          enum: [gateway, tenant, publisher]
        payee_ref: { type: string, nullable: true }
        currency: { type: string }
        max_items: { type: integer, nullable: true }
        minimum_amount: { type: string, nullable: true }
        allocation_entry_ids:
          type: array
          items: { type: string }
        source_event_lane:
          type: string
          enum: [gateway_managed, tenant_delegated, publisher_delegated, owner_managed]
          nullable: true
        source_event_xapp_id: { type: string, nullable: true }
        occurred_from: { type: string, format: date-time, nullable: true }
        occurred_to: { type: string, format: date-time, nullable: true }
        note: { type: string, nullable: true }

    SettlementPayoutProofInput:
      type: object
      required: [proof_kind]
      properties:
        proof_kind: { type: string }
        document_url: { type: string, nullable: true }
        document_hash: { type: string, nullable: true }
        reference_number: { type: string, nullable: true }
        note: { type: string, nullable: true }

    SettlementPayoutProofReviewInput:
      type: object
      required: [action]
      properties:
        action:
          type: string
          enum: [accept, reject]
        note: { type: string, nullable: true }

    SettlementPayoutStatusInput:
      type: object
      required: [status]
      properties:
        status:
          type: string
          enum: [submitted, paid, rejected, cancelled]
        note: { type: string, nullable: true }

    SettlementPayoutPeriodCloseInput:
      type: object
      properties:
        note: { type: string, nullable: true }

    SettlementSplitPolicyComponent:
      type: object
      required:
        [beneficiary_kind, beneficiary_ref, share_type, share_value, display_order, metadata_jsonb]
      properties:
        id: { type: string }
        beneficiary_kind:
          type: string
          enum: [gateway, tenant, publisher]
        beneficiary_ref: { type: string, nullable: true }
        share_type:
          type: string
          enum: [percentage]
        share_value: { type: string }
        display_order: { type: integer }
        metadata_jsonb:
          type: object
          nullable: true
          additionalProperties: true

    SettlementSplitPolicy:
      type: object
      required:
        [
          id,
          scope_kind,
          basis_kind,
          rounding_policy,
          status,
          effective_from,
          version,
          created_at,
          updated_at,
          components,
        ]
      properties:
        id: { type: string }
        scope_kind:
          type: string
          enum: [platform, tenant, publisher, xapp]
        scope_ref: { type: string, nullable: true }
        target_client_id: { type: string, nullable: true }
        target_publisher_id: { type: string, nullable: true }
        target_xapp_id: { type: string, nullable: true }
        target_lane:
          type: string
          enum: [owner_managed, tenant_delegated, publisher_delegated, gateway_managed]
          nullable: true
        currency: { type: string, nullable: true }
        basis_kind: { type: string }
        rounding_policy: { type: string }
        status:
          type: string
          enum: [active, disabled]
        effective_from: { type: string, format: date-time }
        effective_to: { type: string, format: date-time, nullable: true }
        version: { type: string }
        metadata_jsonb:
          type: object
          nullable: true
          additionalProperties: true
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }
        components:
          type: array
          items:
            $ref: "#/components/schemas/SettlementSplitPolicyComponent"

    SettlementSplitPolicyInput:
      type: object
      required: [scope_kind, components]
      properties:
        scope_kind:
          type: string
          enum: [platform, tenant, publisher, xapp]
        scope_ref: { type: string, nullable: true }
        target_client_id: { type: string, nullable: true }
        target_publisher_id: { type: string, nullable: true }
        target_xapp_id: { type: string, nullable: true }
        target_lane:
          type: string
          enum: [owner_managed, tenant_delegated, publisher_delegated, gateway_managed]
          nullable: true
        currency: { type: string, nullable: true }
        basis_kind: { type: string, nullable: true }
        rounding_policy: { type: string, nullable: true }
        status:
          type: string
          enum: [active, disabled]
          nullable: true
        effective_from: { type: string, format: date-time, nullable: true }
        effective_to: { type: string, format: date-time, nullable: true }
        version: { type: string, nullable: true }
        metadata_jsonb:
          type: object
          nullable: true
          additionalProperties: true
        components:
          type: array
          items:
            type: object
            required: [beneficiary_kind, share_value]
            properties:
              beneficiary_kind:
                type: string
                enum: [gateway, tenant, publisher]
              beneficiary_ref: { type: string, nullable: true }
              share_value: { type: string }
              display_order: { type: integer, nullable: true }
              metadata_jsonb:
                type: object
                nullable: true
                additionalProperties: true

    SettlementSplitPolicyEnvelope:
      type: object
      required: [item]
      properties:
        item:
          $ref: "#/components/schemas/SettlementSplitPolicy"

    SettlementSplitPolicyListResponse:
      type: object
      required: [items]
      properties:
        items:
          type: array
          items:
            $ref: "#/components/schemas/SettlementSplitPolicy"

    PaymentSessionCreateResponse:
      allOf:
        - $ref: "#/components/schemas/PaymentSessionEnvelope"
        - type: object
          properties:
            payment_page_url:
              type: string
              format: uri

    GatewayPaymentSessionReadResponse:
      type: object
      required: [result]
      properties:
        result:
          $ref: "#/components/schemas/PaymentSessionObject"

    PaymentSessionCompleteResponse:
      allOf:
        - $ref: "#/components/schemas/PaymentSessionEnvelope"
        - type: object
          properties:
            flow:
              type: string
              enum: [client_collect]
            payment_session_id:
              type: string
            client_settle_url:
              type: string
              format: uri
            provider_reference:
              type: string
              nullable: true
            scheme:
              $ref: "#/components/schemas/PaymentSchemeRegistryDescriptor"
            metadata:
              type: object
              nullable: true
              additionalProperties: true

    GatewayPaymentCompleteImmediateResult:
      type: object
      properties:
        redirect_url:
          type: string
          format: uri
        scheme:
          $ref: "#/components/schemas/PaymentSchemeRegistryDescriptor"

    GatewayPaymentCompleteClientCollectResult:
      type: object
      properties:
        flow:
          type: string
          enum: [client_collect]
        payment_session_id:
          type: string
        client_settle_url:
          type: string
          format: uri
        provider_reference:
          type: string
          nullable: true
        scheme:
          $ref: "#/components/schemas/PaymentSchemeRegistryDescriptor"
        metadata:
          type: object
          additionalProperties: true

    GatewayPaymentCompleteResponse:
      type: object
      properties:
        status:
          type: string
          enum: [success]
        result:
          oneOf:
            - $ref: "#/components/schemas/GatewayPaymentCompleteImmediateResult"
            - $ref: "#/components/schemas/GatewayPaymentCompleteClientCollectResult"

    GatewayPaymentClientSettleResult:
      type: object
      properties:
        redirect_url:
          type: string
          format: uri

    GatewayPaymentClientSettleResponse:
      type: object
      properties:
        status:
          type: string
          enum: [success]
        result:
          $ref: "#/components/schemas/GatewayPaymentClientSettleResult"

    PaymentResourceSummary:
      type: object
      description: Resource metadata associated with a payment requirement.
      additionalProperties: true
      properties:
        xapp_id: { type: string, nullable: true }
        xapp_slug: { type: string, nullable: true }
        tool_name: { type: string, nullable: true }
        description: { type: string, nullable: true }

    PaymentGuardRefResolution:
      type: object
      description: Resolution provenance for `payment_guard_ref` composition.
      additionalProperties: true
      properties:
        payment_guard_ref: { type: string }
        definition_name: { type: string, nullable: true }
        source:
          type: string
          enum: [consumer_manifest, owner_manifest]

    PaymentGuardPricingFloorViolation:
      type: object
      additionalProperties: true
      properties:
        path: { type: string }
        actual: { type: number }
        floor: { type: number }

    PaymentGuardDefinition:
      type: object
      additionalProperties: false
      required: [name]
      properties:
        name: { type: string }
        payment_type: { type: string }
        payment_issuer_mode:
          type: string
          description: |
            Ownership/profile lane for pay-by-request execution and evidence verification.
            `owner_managed` is the unified owner profile (tenant/publisher).
          enum: [owner_managed, gateway_managed, tenant_delegated, publisher_delegated, any]
        payment_scheme: { type: string }
        payment_network: { type: string }
        payment_allowed_issuers:
          type: array
          description: |
            Optional explicit signer issuer allowlist scoped to the selected `payment_issuer_mode`.
            If omitted, runtime uses issuer defaults for the active mode.
            If provided but none of the values are compatible with the active mode, verification fails closed.
          items:
            type: string
            enum: [tenant, publisher, gateway, tenant_delegated, publisher_delegated]
        payment_return_contract: { type: string }
        payment_return_required: { type: boolean }
        payment_return_max_age_s: { type: number }
        payment_return_hmac_secret_refs:
          type: object
          additionalProperties: { type: string }
        payment_return_hmac_secrets:
          type: object
          additionalProperties: { type: string }
        payment_provider_credentials:
          type: object
          additionalProperties: true
          description: |
            Optional provider credential bundle object projected into gateway payment session metadata.
            Runtime expects resolver-backed refs and/or provider bundle refs.
        payment_provider_credentials_refs:
          type: object
          additionalProperties:
            type: object
            additionalProperties: { type: string }
          description: |
            Guard-config alias for provider credential refs keyed by provider.
            Runtime normalizes this to `payment_provider_credentials`.
            Provider nodes may contain flat field refs, `bundle_ref`, or both.
            PayPal full-flow keys are `PAYPAL_CLIENT_ID`, `PAYPAL_CLIENT_SECRET`, `PAYPAL_WEBHOOK_ID`
            (optional `PAYPAL_API_BASE_URL`; `PAYPAL_CHECKOUT_BASE_URL` is compatibility fallback only).
          example:
            stripe:
              bundle_ref: platform://payment:gateway:stripe:bundle
            netopia:
              bundle_ref: platform://payment:gateway:netopia:bundle
            paypal:
              bundle_ref: platform://payment:gateway:paypal:bundle
        payment_provider_secret_refs:
          type: object
          additionalProperties:
            type: object
            additionalProperties: { type: string }
          description: |
            Backward-compatible alias of `payment_provider_credentials_refs`, including `bundle_ref` support.
          example:
            stripe:
              bundle_ref: platform://payment:gateway:stripe:bundle
            netopia:
              bundle_ref: platform://payment:gateway:netopia:bundle
            paypal:
              bundle_ref: platform://payment:gateway:paypal:bundle
        pricing_model: { type: string }
        pricing:
          $ref: "#/components/schemas/PaymentPricingDetails"
        receipt_field: { type: string }
        payment_url: { type: string }
        accepts:
          type: array
          items:
            $ref: "#/components/schemas/PaymentAcceptsEntry"
        policy:
          type: object
          additionalProperties: true
        action:
          type: object
          additionalProperties: true
        owner_override_allowlist:
          type: array
          description: |
            Optional owner-declared allowlist of lower-lane guard config keys allowed to override
            definition defaults (for example `tools`, `pricing.default_amount`, `pricing.tool_overrides.*`).
          items: { type: string }
        owner_pricing_floor:
          type: object
          additionalProperties: false
          description: Optional owner-declared minimum pricing constraints for lower-lane overrides.
          properties:
            default_amount: { type: number }
            xapp_prices:
              type: object
              additionalProperties: { type: number }
            tool_overrides:
              type: object
              additionalProperties: { type: number }

    GuardBlockedGuard:
      type: object
      required: [slug, trigger, reason]
      properties:
        slug: { type: string }
        trigger: { type: string }
        reason: { type: string }
        message: { type: string }
        remediation:
          $ref: "#/components/schemas/SubjectProfileGuardRemediation"
        details:
          type: object
          description: |
            Additional structured details associated with the guard result. Exact fields vary by guard.
            This may include policy/sharing context, monetization state, evidence references, and
            UI/action hints.
          additionalProperties: true
          properties:
            monetization_state:
              $ref: "#/components/schemas/MonetizationStateSummary"
            pricing:
              $ref: "#/components/schemas/PaymentPricingDetails"
            payment_scheme:
              type: string
              nullable: true
            payment_network:
              type: string
              nullable: true
            accepts:
              type: array
              items:
                $ref: "#/components/schemas/PaymentAcceptsEntry"
            resource:
              $ref: "#/components/schemas/PaymentResourceSummary"
            payment_guard_ref_resolution:
              $ref: "#/components/schemas/PaymentGuardRefResolution"
            allowed_override_paths:
              type: array
              items: { type: string }
            disallowed_override_paths:
              type: array
              items: { type: string }
            violations:
              type: array
              items:
                $ref: "#/components/schemas/PaymentGuardPricingFloorViolation"
            remediation:
              $ref: "#/components/schemas/SubjectProfileGuardRemediation"
            guard_ui:
              $ref: "#/components/schemas/SubjectProfileGuardUiDescriptor"
        action:
          type: object
          additionalProperties: true

    SubjectProfileGuardRequirement:
      type: object
      required: [required_profile_family, required_profile_fields]
      properties:
        required_profile_family:
          type: string
          enum: [identity_basic, billing_individual, billing_business]
        required_profile_fields:
          type: array
          items: { type: string }

    SubjectProfileGuardRemediationHandoff:
      type: object
      required: [contract_version, preferred_flow, available_flows, external_endpoint_url]
      properties:
        contract_version:
          type: string
          enum: [subject_profile_remediation_v1]
        preferred_flow:
          type: string
          enum: [internal_guard_ui, hosted_capture, external_endpoint]
        available_flows:
          type: array
          items:
            type: string
            enum: [internal_guard_ui, hosted_capture, external_endpoint]
        external_endpoint_url:
          type: string
          nullable: true

    SubjectProfileGuardCapturePolicy:
      type: object
      required:
        [capture_mode, save_scope, share_targets, acceptance_required_for, dirty_profile_handling]
      properties:
        capture_mode:
          type: string
          enum: [collect_only, collect_and_save]
        save_scope:
          type: string
          enum: [none, subject_private]
        share_targets:
          type: array
          items:
            type: string
            enum: [tenant, publisher, app]
        acceptance_required_for:
          type: array
          items:
            type: string
            enum: [tenant, publisher, app]
        dirty_profile_handling:
          type: string
          enum: [discard_incomplete, allow_incomplete_draft]

    SubjectProfileGuardFormDescriptor:
      type: object
      required:
        [
          contract_version,
          renderer,
          family,
          title,
          description,
          submit,
          schema,
          ui_schema,
          initial_data,
        ]
      properties:
        contract_version:
          type: string
          enum: [subject_profile_form_v1]
        renderer:
          type: string
          enum: [jsonforms]
        family:
          type: string
          enum: [identity_basic, billing_individual, billing_business]
        title:
          type: string
        description:
          type: string
        submit:
          type: object
          required: [payload_field, merge_strategy]
          properties:
            payload_field:
              type: string
              enum: [customerProfile]
            merge_strategy:
              type: string
              enum: [shallow_merge]
        schema:
          type: object
          additionalProperties: true
        ui_schema:
          type: object
          additionalProperties: true
        initial_data:
          type: object
          additionalProperties: true

    SubjectProfileGuardRemediation:
      type: object
      required:
        [
          kind,
          decision,
          reason,
          requirement,
          missing_fields,
          acceptable_profile_families,
          allowed_sources,
          resolved_profile_family,
          evaluated_source,
          handoff,
          capture_policy,
          selection,
          candidates,
          form,
        ]
      properties:
        kind:
          type: string
          enum: [subject_profile_required]
        decision:
          type: string
          enum: [deny]
        reason:
          type: string
          enum: [subject_profile_required]
        requirement:
          $ref: "#/components/schemas/SubjectProfileGuardRequirement"
        missing_fields:
          type: array
          items: { type: string }
        acceptable_profile_families:
          type: array
          items:
            type: string
            enum: [identity_basic, billing_individual, billing_business]
        allowed_sources:
          type: array
          items:
            type: string
            enum:
              [
                request_payload,
                tenant_subject_profile,
                publisher_subject_profile,
                subject_self_profile,
              ]
        resolved_profile_family:
          type: string
          nullable: true
        evaluated_source:
          type: string
        handoff:
          $ref: "#/components/schemas/SubjectProfileGuardRemediationHandoff"
        capture_policy:
          $ref: "#/components/schemas/SubjectProfileGuardCapturePolicy"
        selection:
          $ref: "#/components/schemas/SubjectProfileGuardSelectionContract"
        candidates:
          type: array
          items:
            $ref: "#/components/schemas/SubjectProfileGuardCandidate"
        form:
          $ref: "#/components/schemas/SubjectProfileGuardFormDescriptor"

    SubjectProfileGuardSelectionContract:
      type: object
      required:
        [
          contract_version,
          payload_field,
          selected_profile_id_field,
          selected_profile_source_field,
          nested_selection_field,
          direct_selectable_sources,
          current_selection,
        ]
      properties:
        contract_version:
          type: string
          enum: [subject_profile_selection_v1]
        payload_field:
          type: string
          enum: [customerProfile]
        selected_profile_id_field:
          type: string
          enum: [selected_profile_id]
        selected_profile_source_field:
          type: string
          enum: [selected_profile_source]
        nested_selection_field:
          type: string
          enum: [selection]
        direct_selectable_sources:
          type: array
          items:
            type: string
            enum: [subject_self_profile]
        current_selection:
          nullable: true
          allOf:
            - $ref: "#/components/schemas/SubjectProfileGuardSelectionPayload"

    SubjectProfileGuardSelectionPayload:
      type: object
      required: [source, candidate_id, selected_profile_id]
      properties:
        source:
          type: string
        candidate_id:
          type: string
          nullable: true
        selected_profile_id:
          type: string
          nullable: true
        selected_profile_source:
          type: string
          nullable: true

    SubjectProfileGuardCandidateCapabilities:
      type: object
      required: [can_select, can_edit, can_save_private, can_share, can_refresh_from_source]
      properties:
        can_select: { type: boolean }
        can_edit: { type: boolean }
        can_save_private: { type: boolean }
        can_share: { type: boolean }
        can_refresh_from_source: { type: boolean }

    SubjectProfileGuardCandidateSummary:
      type: object
      required: [name, company_name, email]
      properties:
        name:
          type: string
          nullable: true
        company_name:
          type: string
          nullable: true
        email:
          type: string
          nullable: true

    SubjectProfileGuardCandidate:
      type: object
      required:
        [
          candidate_id,
          label,
          source,
          profile_family,
          selected,
          is_default,
          capabilities,
          selection_payload,
          editable_profile,
          summary,
        ]
      properties:
        candidate_id:
          type: string
          nullable: true
        label:
          type: string
          nullable: true
        source:
          type: string
        profile_family:
          type: string
          nullable: true
        selected:
          type: boolean
        is_default:
          type: boolean
        capabilities:
          $ref: "#/components/schemas/SubjectProfileGuardCandidateCapabilities"
        selection_payload:
          nullable: true
          allOf:
            - $ref: "#/components/schemas/SubjectProfileGuardSelectionPayload"
        editable_profile:
          type: object
          nullable: true
          additionalProperties: true
        summary:
          $ref: "#/components/schemas/SubjectProfileGuardCandidateSummary"

    SubjectProfileGuardUiDescriptor:
      type: object
      required:
        [
          contract_version,
          app_uri,
          xapp_slug,
          widget_name,
          preferred_renderer,
          payload_field,
          remediation,
        ]
      properties:
        contract_version:
          type: string
          enum: [subject_profile_guard_ui_v1]
        app_uri:
          type: string
          enum: [internal://subject-profile-guard]
        xapp_slug:
          type: string
          enum: [subject-profile-check]
        widget_name:
          type: string
          enum: [guard]
        preferred_renderer:
          type: string
          enum: [platform]
        payload_field:
          type: string
          enum: [customerProfile]
        remediation:
          $ref: "#/components/schemas/SubjectProfileGuardRemediation"

    GuardActionHint:
      type: object
      additionalProperties: true
      properties:
        kind:
          type: string
          description: |
            Host-orchestration action hint. Sprint-I baseline kinds include:
            `open_guard`, `complete_payment`, `confirm_action`, `step_up_auth`,
            `upgrade_subscription`, `link_account`, and `complete_subject_profile`.
            Additive extension is allowed; hosts should ignore unknown kinds safely.
        url: { type: string, nullable: true }
        method: { type: string, nullable: true }
        message: { type: string, nullable: true }
        label: { type: string, nullable: true }
        title: { type: string, nullable: true }
        intent: { type: string, nullable: true }
        target: { type: string, nullable: true }

    GuardOrchestrationApproval:
      type: object
      additionalProperties: false
      required: [approved]
      properties:
        approved:
          type: boolean
        trigger:
          type: string
          description: Trigger being approved (for current baseline use `before:tool_run`).
        challenge:
          type: string
          description: Server-issued orchestration challenge token bound to guard/request context.
        payment:
          type: object
          additionalProperties: false
          description: |
            Canonical signed payment evidence attached on orchestration retry for payment guards.
            Used with `xapps_payment_orchestration_v1`.
          required:
            [contract, payment_session_id, status, receipt_id, amount, currency, ts, issuer, sig]
          properties:
            contract:
              type: string
              enum: [xapps_payment_orchestration_v1]
            payment_session_id: { type: string }
            status: { type: string, enum: [paid, pending, failed, canceled, requires_action] }
            receipt_id: { type: string }
            amount: { type: string }
            currency: { type: string }
            ts: { type: string, format: date-time }
            issuer: { type: string }
            subject_id: { type: string }
            installation_id: { type: string }
            client_id: { type: string }
            sig: { type: string }
        meta:
          type: object
          additionalProperties: true
          description: Optional host/tenant metadata for auditing correlation.

    GuardSummary:
      type: object
      description: |
        Redacted, operator/user-facing projection of the latest relevant guard decision for a request.
        This summary is intended for request list/detail UX and should be sufficient for common
        troubleshooting without exposing raw evidence/provenance payloads.
      additionalProperties: true
      properties:
        trigger:
          type: string
          description: Guard trigger that produced the summarized outcome (for example `before:tool_run`).
        outcome:
          type: string
          description: Canonical guard outcome summary (for example `blocked`, `pass`, `error`).
        slug:
          type: string
          description: Guard slug responsible for the summarized outcome.
        reason:
          type: string
          description: |
            Machine-readable reason summary. May use canonical guard reasons or policy deny reason codes
            (for example `subscription_missing`, `payment_receipt_already_used`, `PURPOSE_NOT_ALLOWED`).
        action:
          $ref: "#/components/schemas/GuardActionHint"
        monetization_state:
          $ref: "#/components/schemas/MonetizationStateSummary"
        policy_context_summary:
          $ref: "#/components/schemas/PolicyContextSummary"
      example:
        trigger: "before:tool_run"
        outcome: blocked
        slug: tenant-payment-gate
        reason: ui_orchestration_required
        action:
          kind: open_guard
        monetization_state:
          payment_attempt_state: requires_action
          entitlement_state: unknown
          balance_state: unknown
        policy_context_summary:
          legal_basis: contract
          disclosure_mode: final_answer_only
          requested_data_scope_count: 1

    PolicyContextSummary:
      type: object
      description: |
        Redacted, operator/user-facing summary of sharing-policy context used by a guard decision.
        This is intended for troubleshooting and audit UX without exposing raw subject identifiers
        or full policy payloads.
      additionalProperties: true
      properties:
        purpose:
          type: string
          nullable: true
        legal_basis:
          type: string
          nullable: true
        consent_ref_present:
          type: boolean
          nullable: true
        disclosure_mode:
          type: string
          nullable: true
        requested_data_scope_count:
          type: integer
          nullable: true
        target_subject_present:
          type: boolean
          nullable: true
      example:
        purpose: care_coordination
        legal_basis: contract
        disclosure_mode: final_answer_only
        requested_data_scope_count: 2
        target_subject_present: true

    MonetizationStateSummary:
      type: object
      description: |
        Additive, normalized monetization lifecycle summary for guard/evidence/operator contracts.
        This is a guard-consumable/operator-facing summary (not the billing system of record).
      additionalProperties: true
      properties:
        entitlement_state:
          type: string
          description: |
            Normalized entitlement status (examples: `active`, `inactive`, `grace_period`,
            `trial_active`, `unknown`).
        balance_state:
          type: string
          description: |
            Normalized usage balance status (examples: `sufficient`, `insufficient`, `unknown`).
        payment_attempt_state:
          type: string
          description: |
            Normalized payment attempt status (examples: `paid`, `requires_action`, `failed`,
            `canceled`, `replayed`, `unknown`).
        has_current_access:
          type: boolean
          description: |
            Additive normalized flag showing whether the current monetization state still grants
            usable access now, even when raw entitlement/balance fields require more context.
        tier:
          type: string
          nullable: true
        expires_at:
          type: string
          format: date-time
          nullable: true
        payment_session_id:
          type: string
          nullable: true
        receipt_id:
          type: string
          nullable: true
        issuer:
          type: string
          nullable: true
          description: |
            Redacted issuer/authority identifier for the monetization assertion source used by
            guard/runtime evidence (for example `tenant`, `publisher`, `xapps`). This is
            provenance metadata, not a billing provider identifier contract.
        authority_lane:
          type: string
          enum: [owner_managed, gateway_managed, tenant_delegated, publisher_delegated]
          nullable: true
          description: |
            Optional redacted authority lane for the monetization assertion source (for example
            `owner_managed`, `gateway_managed`, delegated lanes). Additive operator/audit
            provenance metadata; not a provider API contract field.
        source_ref:
          type: string
          nullable: true
          description: |
            Optional redacted source/profile reference for the monetization assertion (for example
            a tenant policy alias or gateway profile handle). Additive provenance metadata only.
      example:
        entitlement_state: active
        balance_state: sufficient
        payment_attempt_state: paid
        has_current_access: true
        tier: pro
        issuer: tenant
        authority_lane: owner_managed
        source_ref: tenant-policy/default

    GuardBlockedErrorResponse:
      type: object
      required: [message, code, guardSlug, trigger, reason, guard]
      properties:
        message: { type: string }
        code:
          type: string
          enum: [GUARD_BLOCKED]
        guardSlug: { type: string }
        guard_slug:
          type: string
          description: Legacy alias for `guardSlug`.
        trigger: { type: string }
        reason:
          type: string
          description: |
            Machine-readable guard reason (additive; hosts should branch on known values and
            fail safely on unknown values). Current commonly-used reasons include:
            `ui_orchestration_required`, `payment_evidence_missing`, `payment_not_settled`,
            `payment_evidence_contract_unsupported`, `payment_evidence_timestamp_invalid`,
            `payment_evidence_expired`, `payment_evidence_malformed`,
            `payment_evidence_secret_missing`,
            `payment_signature_invalid`, `payment_issuer_not_allowed`, `payment_amount_mismatch`,
            `payment_currency_mismatch`, `payment_subject_mismatch`, `payment_context_mismatch`,
            `payment_receipt_already_used`, `price_not_configured`, `subscription_missing`,
            `payment_guard_override_not_allowed`, `payment_guard_pricing_floor_violation`,
            `guard_kind_unsupported`, `subject_required`, `subject_profile_required`,
            `step_up_required`, `maintenance_mode`, `not_linked`, `missing_policy_field`,
            `missing_consent_ref`, `invalid_legal_basis`, `invalid_disclosure_mode`,
            `disclosure_mode_not_allowed`, `requested_scope_not_allowed`, and canonical
            policy deny reason codes (see Spec 10 taxonomy, e.g. `PURPOSE_NOT_ALLOWED`,
            `CONSENT_MISSING`, `RELATIONSHIP_NOT_FOUND`).
        details:
          type: object
          description: |
            Guard-specific structured details (additive). For monetization-sensitive blocking guards
            (for example `before:session_open` / `before:tool_run` / `before:thread_create`
            payment or subscription checks),
            `details.monetization_state` may contain a redacted normalized summary compatible with
            `MonetizationStateSummary`.
          additionalProperties: true
          properties:
            monetization_state:
              $ref: "#/components/schemas/MonetizationStateSummary"
            pricing:
              $ref: "#/components/schemas/PaymentPricingDetails"
            payment_scheme:
              type: string
              nullable: true
            payment_network:
              type: string
              nullable: true
            accepts:
              type: array
              items:
                $ref: "#/components/schemas/PaymentAcceptsEntry"
            resource:
              $ref: "#/components/schemas/PaymentResourceSummary"
            payment_guard_ref_resolution:
              $ref: "#/components/schemas/PaymentGuardRefResolution"
            allowed_override_paths:
              type: array
              items: { type: string }
            disallowed_override_paths:
              type: array
              items: { type: string }
            violations:
              type: array
              items:
                $ref: "#/components/schemas/PaymentGuardPricingFloorViolation"
            remediation:
              $ref: "#/components/schemas/SubjectProfileGuardRemediation"
        action:
          type: object
          additionalProperties: true
        remediation:
          $ref: "#/components/schemas/SubjectProfileGuardRemediation"
        guard:
          $ref: "#/components/schemas/GuardBlockedGuard"

    PredicateScopeValidationRequest:
      type: object
      required: [scopes]
      additionalProperties: false
      properties:
        scopes:
          type: array
          items: { type: string }
        request:
          type: object
          additionalProperties: false
          properties:
            method: { type: string }
            path: { type: string }
            query:
              type: object
              additionalProperties: { type: string }
            context:
              type: object
              additionalProperties: false
              properties:
                clientId: { type: string }
                installationId: { type: string }
                xappId: { type: string }
                subjectId: { type: string }
                publisherId: { type: string }
                requestId: { type: string }
                userEmail: { type: string }
                orgId: { type: string }
                userId: { type: string }
                resourceType: { type: string }
                resourceId: { type: string }

    PredicateScopeValidationResponse:
      type: object
      properties:
        allowed: { type: boolean }
        matchedScopes:
          type: array
          items: { type: string }
        hookConfigured: { type: boolean }
        results:
          type: array
          items:
            $ref: "#/components/schemas/PredicateScopeValidationResult"

    PredicateScopeValidationResult:
      type: object
      properties:
        scope: { type: string }
        baseScope: { type: string }
        valid: { type: boolean }
        errors:
          type: array
          items: { type: string }
        warnings:
          type: array
          items: { type: string }
        unknownPredicates:
          type: array
          items: { type: string }
        matches: { type: boolean }
        reason:
          type: string
          description: |
            Machine-readable guard reason (additive examples shared with `GUARD_BLOCKED` flows):
            `ui_orchestration_required`, `payment_evidence_missing`, `payment_not_settled`,
            `payment_signature_invalid`, `payment_issuer_not_allowed`, `payment_amount_mismatch`,
            `payment_currency_mismatch`, `payment_subject_mismatch`, `payment_context_mismatch`,
            `payment_receipt_already_used`, `price_not_configured`, `subscription_missing`,
            `payment_guard_override_not_allowed`, `payment_guard_pricing_floor_violation`,
            `subject_required`, `subject_profile_required`, `step_up_required`,
            `maintenance_mode`, and canonical policy deny reason codes such as
            `PURPOSE_NOT_ALLOWED`, `CONSENT_MISSING`, `RELATIONSHIP_NOT_FOUND`.

    XappsFormConfig:
      type: object
      description: |
        Optional, declarative form presentation hints for hosted widgets and reference UIs.
        Located at `tool.input_ui_schema.xapps.form`.

        This config MUST remain non-executable and is intended only for presentation.
      properties:
        order:
          type: array
          description: Ordered list of top-level field names.
          items: { type: string }
        widgets:
          type: object
          description: Widget hints keyed by dotted field path.
          additionalProperties:
            type: object
            properties:
              widget:
                type: string
                description: Presentation widget hint (e.g. input, textarea, select, date).
              placeholder:
                type: string
              helpText:
                type: string
              hidden:
                type: boolean

    XappsRenderConfig:
      type: object
      description: |
        Optional, manifest-driven response rendering config used by hosted platform widgets.
        Located at `tool.output_ui_schema.xapps.render`.
      required: [kind]
      properties:
        kind:
          type: string
          enum: [kv, list, json]
        title:
          type: string
          description: Optional panel title.
      oneOf:
        - title: kv
          required: [kind, fields]
          properties:
            kind: { type: string, enum: [kv] }
            fields:
              type: array
              maxItems: 100
              items:
                type: object
                required: [path]
                properties:
                  label: { type: string }
                  path:
                    type: string
                    description: Dotted path into the decrypted response result.
        - title: list
          required: [kind]
          properties:
            kind: { type: string, enum: [list] }
            itemsPath:
              type: string
              description: Dotted path to an array in the decrypted response result.
              default: items
            titlePath:
              type: string
              description: Dotted path to a display title within each item.
              default: title
            urlPath:
              type: string
              description: Dotted path to a URL within each item (http/https only).
              default: url
        - title: json
          required: [kind]
          properties:
            kind: { type: string, enum: [json] }

    ToolUiSchema:
      type: object
      description: Optional tool UI schema.
      properties:
        xapps:
          type: object
          properties:
            endpointEnv:
              type: string
              description: |
                Optional execution routing hint used by the gateway worker to select which configured
                `endpoints.<env>` entry to dispatch to for this tool.

                If omitted, the worker will use the first configured endpoint for the xapp version.

                NOTE: this is an execution concern (not presentation). It currently lives under
                `tool.output_ui_schema.xapps.endpointEnv` for historical reasons.
            render:
              $ref: "#/components/schemas/XappsRenderConfig"
            form:
              $ref: "#/components/schemas/XappsFormConfig"
            manual_response:
              type: boolean
              description: If true, completion requires manual publisher intervention via their console.
    SubjectKey:
      type: object
      properties:
        id: { type: string }
        subjectId: { type: string }
        installationId: { type: string }
        publicKey: { type: string }
        label: { type: string }
        status: { type: string, enum: [pending_verification, active, revoked, expired] }
        algorithm: { type: string }
        metadata: { type: object }
        createdAt: { type: string, format: date-time }
        lastUsedAt: { type: string, format: date-time, nullable: true }
        expiresAt: { type: string, format: date-time, nullable: true }

    Request:
      type: object
      properties:
        id: { type: string }
        status:
          type: string
          enum: [RECEIVED, QUEUED, PROCESSING, COMPLETED, FAILED, CANCELLED, EXPIRED]
        tool_name: { type: string }
        created_at: { type: string, format: date-time }

    Installation:
      type: object
      properties:
        id: { type: string }
        client_id: { type: string }
        xapp_id: { type: string }
        status: { type: string }
        version_strategy: { type: string }
        pinned_version_id: { type: string, nullable: true }

    Xapp:
      type: object
      properties:
        id: { type: string }
        publisher_id: { type: string }
        name: { type: string }
        slug: { type: string }
        description: { type: string, nullable: true }
        visibility: { type: string }
        status: { type: string }

    XappVersion:
      type: object
      properties:
        id: { type: string }
        xapp_id: { type: string }
        version: { type: string }
        status: { type: string }
        published_at: { type: string, format: date-time, nullable: true }
        manifest_jsonb: { $ref: "#/components/schemas/JsonValue" }

    XappTool:
      type: object
      properties:
        id: { type: string }
        xapp_version_id: { type: string }
        tool_name: { type: string }
        title: { type: string }
        description: { type: string, nullable: true }
        async: { type: boolean }
        input_schema_jsonb:
          $ref: "#/components/schemas/JsonValue"
        output_schema_jsonb:
          $ref: "#/components/schemas/JsonValue"
        input_ui_schema_jsonb:
          $ref: "#/components/schemas/ToolUiSchema"
        output_ui_schema_jsonb:
          $ref: "#/components/schemas/ToolUiSchema"
        signature_policy:
          type: string
          enum: [none, ed25519]
          default: none
        polling_jsonb:
          $ref: "#/components/schemas/JsonValue"
        artifacts_jsonb:
          $ref: "#/components/schemas/JsonValue"

    XappWidget:
      type: object
      properties:
        id: { type: string }
        xapp_version_id: { type: string }
        widget_name: { type: string }
        type: { type: string }
        renderer: { type: string }
        bind_tool_name: { type: string, nullable: true }
        entry_jsonb: { type: object }
        capabilities_jsonb: { type: array, items: { type: string } }
        required_context_jsonb: { type: object }
        ui_override_jsonb: { type: object }
        config_jsonb: { type: object }

    Manifest:
      type: object
      required: [name, slug, version, tools, widgets]
      additionalProperties: false
      properties:
        title: { type: string }
        name: { type: string }
        slug: { type: string }
        version: { type: string }
        description: { type: string }
        image: { type: string }
        tags:
          type: array
          items: { type: string }
        terms:
          type: object
          additionalProperties: false
          properties:
            title: { type: string }
            text: { type: string }
            url: { type: string }
            version: { type: string }
        visibility: { type: string, description: "public|private (platform-defined)" }
        metadata:
          $ref: "#/components/schemas/JsonValue"
        guards:
          type: array
          description: Additive guard declarations by lifecycle trigger.
          items:
            type: object
            required: [slug, trigger]
            additionalProperties: false
            properties:
              slug: { type: string }
              trigger:
                type: string
                enum:
                  [
                    "before:installation_create",
                    "before:widget_load",
                    "before:session_open",
                    "before:tool_run",
                    "before:thread_create",
                    "after:install",
                    "after:link_complete",
                    "before:link_revoke",
                    "before:uninstall",
                    "after:tool_complete",
                    "after:uninstall",
                  ]
              headless: { type: boolean }
              blocking: { type: boolean }
              implements: { type: string }
              config:
                type: object
                additionalProperties: true
                description: |
                  Optional guard runtime config. For `before:tool_run`, policy-pack guards may use
                  `config.policy` with additive baseline keys:
                  - `kind` (subscription|payment|step_up|stepup|confirmation|allow|pass|deny|fail)
                  - `pass_if_any[]`, `pass_if_all[]`, `fail_if_any[]`
                  - `reason`, `message` (optional override aliases also allowed at config root)
                  Baseline runtime is strict contract mode (`policy.kind`/`guard_kind`);
                  exemplar slug fallback is compatibility-only via `GUARD_EXEMPLAR_SLUG_FALLBACK_ENABLED=true`.
                example:
                  policy:
                    kind: subscription
                    pass_if_any: [pro, enterprise]
                    fail_if_any: [suspended]
                  reason: tier_required
                  message: Subscription tier required
                  tools: [deep_analysis]
        guards_policy:
          type: object
          additionalProperties: false
          description: |
            Guard chaining policy per trigger. Default is `all` + `stop_on_fail` when omitted.
            Supports either:
            - string shorthand: `all` | `any`
            - object form: `{ mode: "all"|"any", on_fail: "stop_on_fail"|"continue_on_fail" }`
          properties:
            "before:installation_create":
              oneOf:
                - type: string
                  enum: [all, any]
                - type: object
                  additionalProperties: false
                  properties:
                    mode: { type: string, enum: [all, any] }
                    on_fail: { type: string, enum: [stop_on_fail, continue_on_fail] }
            "before:widget_load":
              oneOf:
                - type: string
                  enum: [all, any]
                - type: object
                  additionalProperties: false
                  properties:
                    mode: { type: string, enum: [all, any] }
                    on_fail: { type: string, enum: [stop_on_fail, continue_on_fail] }
            "before:session_open":
              oneOf:
                - type: string
                  enum: [all, any]
                - type: object
                  additionalProperties: false
                  properties:
                    mode: { type: string, enum: [all, any] }
                    on_fail: { type: string, enum: [stop_on_fail, continue_on_fail] }
            "before:tool_run":
              oneOf:
                - type: string
                  enum: [all, any]
                - type: object
                  additionalProperties: false
                  properties:
                    mode: { type: string, enum: [all, any] }
                    on_fail: { type: string, enum: [stop_on_fail, continue_on_fail] }
            "before:thread_create":
              oneOf:
                - type: string
                  enum: [all, any]
                - type: object
                  additionalProperties: false
                  properties:
                    mode: { type: string, enum: [all, any] }
                    on_fail: { type: string, enum: [stop_on_fail, continue_on_fail] }
            "after:install":
              oneOf:
                - type: string
                  enum: [all, any]
                - type: object
                  additionalProperties: false
                  properties:
                    mode: { type: string, enum: [all, any] }
                    on_fail: { type: string, enum: [stop_on_fail, continue_on_fail] }
            "after:link_complete":
              oneOf:
                - type: string
                  enum: [all, any]
                - type: object
                  additionalProperties: false
                  properties:
                    mode: { type: string, enum: [all, any] }
                    on_fail: { type: string, enum: [stop_on_fail, continue_on_fail] }
            "before:link_revoke":
              oneOf:
                - type: string
                  enum: [all, any]
                - type: object
                  additionalProperties: false
                  properties:
                    mode: { type: string, enum: [all, any] }
                    on_fail: { type: string, enum: [stop_on_fail, continue_on_fail] }
            "before:uninstall":
              oneOf:
                - type: string
                  enum: [all, any]
                - type: object
                  additionalProperties: false
                  properties:
                    mode: { type: string, enum: [all, any] }
                    on_fail: { type: string, enum: [stop_on_fail, continue_on_fail] }
            "after:tool_complete":
              oneOf:
                - type: string
                  enum: [all, any]
                - type: object
                  additionalProperties: false
                  properties:
                    mode: { type: string, enum: [all, any] }
                    on_fail: { type: string, enum: [stop_on_fail, continue_on_fail] }
            "after:uninstall":
              oneOf:
                - type: string
                  enum: [all, any]
                - type: object
                  additionalProperties: false
                  properties:
                    mode: { type: string, enum: [all, any] }
                    on_fail: { type: string, enum: [stop_on_fail, continue_on_fail] }
          example:
            "before:tool_run":
              mode: all
              on_fail: stop_on_fail
            "before:widget_load": any
        linking:
          type: object
          required: [strategy, setup_url]
          additionalProperties: false
          properties:
            strategy: { type: string, enum: [platform_v1, custom] }
            setup_url: { type: string }
            guard_slug: { type: string }
        connectivity:
          type: object
          additionalProperties: false
          properties:
            executor_mode:
              type: string
              enum: [PUBLIC_EXECUTOR, PRIVATE_EXECUTOR, AGENT_TUNNEL]
            auth_mode:
              type: string
              enum: [PUBLISHER_APP, USER_DELEGATED, HYBRID]
            signing_policy:
              type: string
              enum: [none, subject_proof, publisher_proof]
            webhooks:
              type: object
              additionalProperties: true
            proxy_policy:
              type: object
              additionalProperties: true
        payment_guard_definitions:
          type: array
          description: Reusable payment policy definitions referenced by guards via `payment_guard_ref`.
          items:
            $ref: "#/components/schemas/PaymentGuardDefinition"
        tools:
          type: array
          items:
            type: object
            required: [tool_name, title, input_schema, output_schema]
            additionalProperties: false
            properties:
              tool_name: { type: string }
              title: { type: string }
              description: { type: string }
              async: { type: boolean }
              input_schema: { $ref: "#/components/schemas/JsonValue" }
              output_schema: { $ref: "#/components/schemas/JsonValue" }
              input_ui_schema: { $ref: "#/components/schemas/ToolUiSchema" }
              output_ui_schema: { $ref: "#/components/schemas/ToolUiSchema" }
              signature_policy:
                type: string
                enum: [none, ed25519]
                default: none
              adapter:
                type: object
                additionalProperties: false
                required: [method, path]
                properties:
                  method:
                    type: string
                    enum: [GET, POST, PUT, DELETE, PATCH]
                  path:
                    type: string
                  query:
                    type: object
                    additionalProperties: true
                  headers:
                    type: object
                    additionalProperties: true
                  body:
                    $ref: "#/components/schemas/JsonValue"
                  response_mapping:
                    type: object
                    additionalProperties: true
        widgets:
          type: array
          items:
            type: object
            required: [widget_name, type, renderer]
            additionalProperties: false
            properties:
              widget_name: { type: string }
              type: { type: string, enum: [read, write] }
              renderer: { type: string, enum: [platform, publisher, json-forms, ui-kit, app-shell] }
              bind_tool_name: { type: string, nullable: true }
              entry:
                type: object
                description: Widget entrypoint configuration.
                additionalProperties: false
                properties:
                  kind: { type: string, enum: [platform_template, iframe_url] }
                  template: { type: string, description: "When kind=platform_template" }
                  url: { type: string, description: "When kind=iframe_url" }
              capabilities:
                type: array
                description: Optional capability gating for widget tokens.
                items: { type: string }
              required_context: { type: object }
              ui_override: { type: object }
              config: { type: object }
        event_subscriptions:
          type: array
          items:
            type: object
            required: [event_type, webhook_url]
            additionalProperties: false
            properties:
              event_type: { type: string }
              webhook_url: { type: string }
        endpoints:
          type: object
          description: Map of endpoint environment name to config.
          additionalProperties:
            type: object
            required: [base_url]
            additionalProperties: false
            properties:
              base_url:
                type: string
                description: Must be https:// or internal://
              timeout_ms: { type: number }
              health_check:
                type: object
                additionalProperties: false
                properties:
                  path: { type: string }
                  interval_s: { type: number }
              retry_policy:
                type: object
                additionalProperties: false
                properties:
                  max_retries: { type: number }
                  backoff: { type: string, enum: [none, linear, exponential] }
              region: { type: string }
              labels:
                type: object
                additionalProperties: { type: string }
        endpoint_groups:
          type: object
          description: Optional endpoint-group routing strategy map.
          additionalProperties:
            type: object
            additionalProperties: false
            required: [strategy, endpoints]
            properties:
              strategy:
                type: string
                enum: [failover, round_robin, region_affinity]
              endpoints:
                type: array
                items: { type: string }
              region_map:
                type: object
                additionalProperties: { type: string }

    XappEndpoint:
      type: object
      properties:
        id: { type: string }
        xapp_version_id: { type: string }
        env: { type: string }
        base_url: { type: string }
        timeout_ms: { type: number }
        config_jsonb: { $ref: "#/components/schemas/JsonValue" }

    EndpointCredential:
      type: object
      properties:
        id: { type: string }
        xapp_endpoint_id: { type: string }
        auth_type: { type: string, enum: [hmac, mtls, oauth2_cc, api_key_header, none] }
        active_kid: { type: string, nullable: true }
        expires_at: { type: string, format: date-time, nullable: true }
        last_rotated_at: { type: string, format: date-time, nullable: true }
        rotation_health:
          type: object
          properties:
            status:
              type: string
              enum: [healthy, expiring_soon, expired, stale, missing_active_key]
            days_until_expiry: { type: number, nullable: true }
            days_since_rotation: { type: number, nullable: true }
        config_jsonb: { $ref: "#/components/schemas/EndpointCredentialConfig" }

    EndpointCredentialConfig:
      type: object
      description: Connector routing configuration used by the gateway worker for external HTTP connectors.
      properties:
        submitPath:
          type: string
          example: /xapps/requests
        pollPath:
          type: string
          example: /xapps/requests/{externalRequestId}/status
        resultPath:
          type: string
          example: /xapps/requests/{externalRequestId}/result
        tokenUrl: { type: string }
        clientId: { type: string }
        clientSecret: { type: string }
        scope: { type: string }
        headerName: { type: string }
        headerValue: { type: string }
        queryName: { type: string }
        queryValue: { type: string }
        clientCertPem: { type: string }
        clientKeyPem: { type: string }
        caPem: { type: string }
        rejectUnauthorized: { type: boolean }

    CredentialKey:
      type: object
      properties:
        id: { type: string }
        endpoint_credential_id: { type: string }
        kid: { type: string }
        status: { type: string }
        algorithm: { type: string, enum: [hmac-sha256, hmac-sha512, ed25519] }
        not_before: { type: string, format: date-time }
        not_after: { type: string, format: date-time }
        expires_at: { type: string, format: date-time, nullable: true }
        # Secrets are not returned by the API
        secret_ref:
          type: string
          nullable: true
          description: "Resolved at runtime via shared secret resolver. Supports env:, conditional vault://, and conditional awssm://"
        created_at: { type: string, format: date-time }

    EvidenceEnvelope:
      type: object
      required: [schema_version, type, request_id, occurred_at, data]
      properties:
        schema_version:
          type: string
          enum: ["1.0"]
        type:
          type: string
          enum:
            [
              GUARD_DECISION,
              DISPATCH_SELECTED,
              CREDENTIAL_BOUND,
              SECRET_RESOLVED,
              PROJECTION_APPLIED,
            ]
        request_id: { type: string }
        chain_id: { type: string }
        step_id: { type: string }
        decision_id:
          type: string
          description: Runtime-generated decision lineage identifier for this evidence record.
        parent_decision_id:
          type: string
          description: Optional parent decision lineage identifier when this decision derives from an earlier one.
        occurred_at: { type: string, format: date-time }
        data:
          type: object
          additionalProperties: true

    RequestEvidencePayload:
      type: object
      additionalProperties: true
      properties:
        evidence_schema_version:
          type: string
          enum: ["1.0"]
        policy_decision_id: { type: string }
        disclosure_decision_id: { type: string }
        provenance:
          type: object
          additionalProperties: false
          properties:
            policy_rule_ref: { type: string, nullable: true }
            consent_ref: { type: string, nullable: true }
            legal_basis_ref: { type: string, nullable: true }
            attestation_ref: { type: string, nullable: true }
        provenance_profile: { type: string }
        provenance_required_fields:
          type: array
          items: { type: string }
        selected_endpoint_ref: { type: string }
        selected_endpoint_id: { type: string }
        endpoint_ref: { type: string }
        auth_lane: { type: string }
        credential_profile: { type: string }
        key_ref:
          type: object
          additionalProperties: true
          properties:
            kid: { type: string }
        provider_id: { type: string }
        provider: { type: string, description: "Legacy alias; canonical field is provider_id." }
        reference_fingerprint: { type: string }
        fallback_used: { type: boolean }
        outcome: { type: string }
        disclosure_mode: { type: string }
        evidence_envelope: { $ref: "#/components/schemas/EvidenceEnvelope" }

    RequestEvent:
      type: object
      required: [id, request_id, type, created_at]
      properties:
        id: { type: string }
        request_id: { type: string }
        type: { type: string }
        message: { type: string, nullable: true }
        data_jsonb:
          oneOf:
            - { $ref: "#/components/schemas/RequestEvidencePayload" }
            - { type: "null" }
        created_at: { type: string, format: date-time }

    PlatformGuardCatalogEntry:
      type: object
      required: [slug, mode, maturity, triggers, description]
      properties:
        slug: { type: string }
        mode: { type: string, enum: [headless, ui, hybrid] }
        maturity: { type: string, enum: [baseline, exemplar, planned] }
        triggers:
          type: array
          items: { type: string }
        description: { type: string }

    Event:
      type: object
      properties:
        id: { type: string }
        type: { type: string }
        payload: { $ref: "#/components/schemas/JsonValue" }
        client_id: { type: string, nullable: true }
        installation_id: { type: string, nullable: true }
        xapp_id: { type: string, nullable: true }
        occurred_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }

    EventDelivery:
      type: object
      properties:
        id: { type: string }
        event_id: { type: string }
        installation_id: { type: string }
        delivery_url: { type: string }
        status: { type: string, enum: [pending, completed, failed, dead_letter] }
        attempts: { type: number }
        latency_ms: { type: integer, nullable: true }
        last_attempt_at: { type: string, format: date-time, nullable: true }
        last_error: { type: string, nullable: true }
        next_attempt_at: { type: string, format: date-time, nullable: true }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    PublisherWebhookTarget:
      type: object
      properties:
        id: { type: string }
        publisher_id: { type: string }
        event_type: { type: string }
        webhook_url: { type: string }
        signing_secret_ref:
          type: string
          nullable: true
          description: "Secret reference for managed webhook signing (supported: env:NAME; vault://... when VAULT_ADDR + VAULT_TOKEN are configured; awssm://... when AWS_REGION + AWS credentials are configured)"
        status: { type: string, enum: [active, disabled] }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    PublisherWebhookTargetInput:
      type: object
      properties:
        eventType: { type: string }
        webhookUrl: { type: string }
        status: { type: string, enum: [active, disabled] }
        signingSecretRef:
          type: string
          description: "Optional managed signing secret reference (supported: env:NAME; vault://... when VAULT_ADDR + VAULT_TOKEN are configured; awssm://... when AWS_REGION + AWS credentials are configured)"

    PublisherEgressTarget:
      type: object
      properties:
        id: { type: string }
        publisher_id: { type: string }
        name: { type: string }
        base_url: { type: string }
        status: { type: string, enum: [active, disabled] }
        auth_type: { type: string, enum: [none, bearer, header, query] }
        auth_header_name: { type: string, nullable: true }
        auth_query_name: { type: string, nullable: true }
        has_secret: { type: boolean }
        secret_ref:
          type: string
          nullable: true
          description: "Resolved at runtime via shared secret resolver. Supports env:, conditional vault://, and conditional awssm://"
        allowed_methods:
          type: array
          items: { type: string }
        allowed_path_prefixes:
          type: array
          items: { type: string }
        policy:
          $ref: "#/components/schemas/PublisherEgressPolicy"
        quota:
          $ref: "#/components/schemas/PublisherEgressQuota"
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    PublisherEgressTargetInput:
      type: object
      properties:
        name: { type: string }
        baseUrl: { type: string }
        status: { type: string, enum: [active, disabled] }
        authType: { type: string, enum: [none, bearer, header, query] }
        authHeaderName: { type: string }
        authQueryName: { type: string }
        secret: { type: string }
        secretRef:
          type: string
          description: "Secret reference (supported: env:NAME; vault://... when VAULT_ADDR + VAULT_TOKEN are configured; awssm://... when AWS_REGION + AWS credentials are configured)"
        clearSecret: { type: boolean }
        allowedMethods:
          type: array
          items: { type: string }
        allowedPathPrefixes:
          type: array
          items: { type: string }
        policy:
          type: object
          properties:
            allowlistOnly: { type: boolean }
            deniedMethods:
              type: array
              items: { type: string }
            deniedPathPatterns:
              type: array
              items: { type: string }
        quota:
          type: object
          properties:
            burstPerMinute: { type: integer, minimum: 0 }
            dailyLimit: { type: integer, minimum: 0 }
            monthlyLimit: { type: integer, minimum: 0 }

    PublisherEgressPolicy:
      type: object
      properties:
        allowlist_only: { type: boolean }
        denied_methods:
          type: array
          items: { type: string }
        denied_path_patterns:
          type: array
          items: { type: string }

    PublisherEgressQuota:
      type: object
      properties:
        burst_per_minute: { type: integer, nullable: true }
        daily_limit: { type: integer, nullable: true }
        monthly_limit: { type: integer, nullable: true }

    PublisherEgressAuditLog:
      type: object
      properties:
        id: { type: string }
        publisher_id: { type: string }
        client_id: { type: string, nullable: true }
        installation_id: { type: string, nullable: true }
        request_id: { type: string, nullable: true }
        target_id: { type: string }
        tool_name: { type: string, nullable: true }
        route_path: { type: string, nullable: true }
        method: { type: string }
        target_url: { type: string }
        status_code: { type: integer, nullable: true }
        status_family: { type: string, nullable: true }
        latency_ms: { type: integer }
        success: { type: boolean }
        error_message: { type: string, nullable: true }
        usage_units: { type: integer }
        created_at: { type: string, format: date-time }

    ClientInstallationPolicy:
      type: object
      properties:
        mode:
          type: string
          enum: [manual, auto_available]
          description: |
            `manual`: subject installations are explicitly managed with add/remove actions.
            `auto_available`: the marketplace behaves as available-by-default for subjects and
            resolves installation under the hood when the app is opened.
        update_mode:
          type: string
          enum: [manual, auto_update_compatible]
          description: |
            `manual`: updates remain explicit user actions.
            `auto_update_compatible`: when a compatible update is available, the host may resolve
            that update automatically as part of the open flow.

    PublisherMockWebhook:
      type: object
      properties:
        id: { type: string }
        publisher_id: { type: string }
        xapp_id: { type: string }
        event_type: { type: string }
        payload: { $ref: "#/components/schemas/JsonValue" }
        headers: { $ref: "#/components/schemas/JsonValue" }
        received_at: { type: string, format: date-time }

    Tenant:
      type: object
      properties:
        id: { type: string }
        name: { type: string }
        slug: { type: string }
        website_url: { type: string, nullable: true }
        company_name: { type: string, nullable: true }
        api_url: { type: string, nullable: true }
