openapi: 3.1.0
info:
  title: CYBEXO Pulse Public Scan API
  version: 1.0.0
  description: Bounded public HTML diagnostics with explicit evidence gaps and expiring report links. A public scan does not enroll a website in monitoring or certify consent behavior.
servers:
  - url: https://pulse.cybexo.com
paths:
  /v2/public-scan/healthz:
    get:
      summary: Check public scan service health.
      operationId: getPulsePublicScanHealth
      responses:
        "200":
          description: Service is available.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
  /v2/public-scan/scan:
    post:
      summary: Review public HTML and create a fresh report link.
      operationId: createPulsePublicScan
      parameters:
        - name: domain
          in: query
          required: false
          schema:
            type: string
          description: Public hostname or HTTP(S) URL. The hostname is normalized and scanning begins at its HTTPS origin, without the supplied path or query. Prefer the JSON request body for new integrations.
        - name: maxPages
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
          description: Requested page limit, default 100 subject to service caps. Time, discovery, request, and response-size limits can stop the scan earlier.
      requestBody:
        required: false
        description: Prefer a JSON body. Query-only domain requests remain supported; a domain is required in one location.
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PublicScanRequest"
            examples:
              scanDomain:
                summary: Scan a production domain
                value:
                  domain: cmp.cybexo.com
                  maxPages: 1
      responses:
        "200":
          description: Scan processing completed. Inspect assessment and coverage; HTTP success does not mean the website passed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicScanResponse"
        "400":
          description: Invalid scan request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "408":
          description: Request body was not received within five seconds. Send the complete JSON body and retry explicitly.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "413":
          description: Request body exceeds the permitted size.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "429":
          description: Rate limit exceeded. Respect Retry-After before retrying.
          headers:
            Retry-After:
              description: Seconds to wait before retrying.
              schema:
                type: integer
                minimum: 1
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "503":
          description: Scan admission is temporarily unavailable. Retry later.
          headers:
            Retry-After:
              description: Seconds to wait before retrying.
              schema:
                type: integer
                minimum: 1
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "500":
          description: Public scan service error.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /v2/public-scan/report/{token}:
    get:
      summary: Retrieve a public scan report by token.
      operationId: getPulsePublicScanReport
      parameters:
        - name: token
          in: path
          required: true
          schema:
            type: string
            minLength: 1
          description: New public report tokens are 32 hexadecimal characters. Older monitoring share tokens remain supported under their separate retention policy.
      responses:
        "200":
          description: Report found.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicReportResponse"
        "404":
          description: Report is unknown or expired.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
components:
  schemas:
    HealthResponse:
      type: object
      required:
        - ok
        - data
        - meta
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          required:
            - status
            - service
          properties:
            status:
              type: string
              example: ok
            service:
              type: string
              example: public_scan
        meta:
          $ref: "#/components/schemas/ResponseMeta"
    PublicScanRequest:
      type: object
      description: Every scan receives a new server-generated report token. A supplied token is ignored and cannot replace an existing report.
      required:
        - domain
      properties:
        domain:
          type: string
          description: Public hostname or HTTP(S) URL. The hostname is normalized and scanning begins at its HTTPS origin, without the supplied path or query.
          examples:
            - cmp.cybexo.com
            - https://example.com
        maxPages:
          type: integer
          minimum: 1
          description: Requested page limit, default 100 subject to service caps. Time, discovery, request, and response-size limits can stop the scan earlier.
      additionalProperties: false
    PublicScanResponse:
      type: object
      required:
        - ok
        - data
        - meta
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          required:
            - scan
            - report
          properties:
            scan:
              $ref: "#/components/schemas/PublicScan"
            report:
              $ref: "#/components/schemas/PublicReportReference"
        meta:
          $ref: "#/components/schemas/ResponseMeta"
    PublicReportResponse:
      type: object
      required:
        - ok
        - data
        - meta
      properties:
        ok:
          type: boolean
          const: true
        data:
          type: object
          required:
            - client
            - scan
          properties:
            client:
              type: object
              required:
                - domain
              properties:
                domain:
                  type: string
            scan:
              $ref: "#/components/schemas/PublicScan"
            generatedAt:
              type: string
              format: date-time
              description: Present on new public reports; older monitoring reports may omit it.
            expiresAt:
              type: string
              format: date-time
              description: Exact expiry of a new public report, no later than seven days after creation. Older monitoring reports may omit this field and follow their separate retention policy.
        meta:
          $ref: "#/components/schemas/ResponseMeta"
    PublicReportReference:
      type: object
      description: Immutable reference to this scan, readable by anyone with the link. Each scan creates a fresh token; downloaded copies are not removed by expiry.
      required:
        - token
        - url
        - json_url
        - expiresAt
      properties:
        token:
          type: string
          pattern: "^[a-fA-F0-9]{32}$"
        url:
          type: string
          format: uri
          description: Human-readable public report URL.
        json_url:
          type: string
          description: Relative JSON report endpoint.
        expiresAt:
          type: string
          format: date-time
          description: Exact deadline for retrieving this public report, within seven days of creation.
    PublicScan:
      type: object
      description: New scans include coverage and assessment. Treat their absence in older saved reports as unknown, never as a pass.
      required:
        - timestamp
        - domain
        - pagesTested
        - okCount
        - issueCount
        - issues
        - pages
      properties:
        timestamp:
          type: string
          format: date-time
        domain:
          type: string
        pagesTested:
          type: integer
          minimum: 0
          description: HTML pages checked, equal to coverage.tested when coverage is available.
        okCount:
          type: integer
          minimum: 0
          description: Pages explicitly assessed as pass. Never calculate this by subtracting issueCount from pagesTested.
        issueCount:
          type: integer
          minimum: 0
          description: Pages with a failed content assessment, excluding acquisition failures. Not a finding count.
        assessment:
          $ref: "#/components/schemas/PageAssessment"
        coverage:
          $ref: "#/components/schemas/ScanCoverage"
        issues:
          type: array
          description: Failed pages with their findings; may include acquisition failures that are excluded from issueCount.
          items:
            $ref: "#/components/schemas/PublicScanIssuePage"
        pages:
          type: array
          items:
            $ref: "#/components/schemas/PublicScanPage"
    PublicScanPage:
      type: object
      description: Page source evidence or an acquisition/exclusion result. A failed availability assessment does not imply that HTML was tested.
      required:
        - url
        - ok
      properties:
        url:
          type: string
          format: uri
          description: Credentials and fragments removed, query values redacted. Paths and query parameter names remain and may contain sensitive information.
        ok:
          type: boolean
          description: Compatibility flag; use the assessment and evidence rather than inferring a consent pass.
        assessment:
          $ref: "#/components/schemas/PageAssessment"
        status:
          type: integer
          description: HTTP response status when available.
        check_results:
          type: array
          items:
            $ref: "#/components/schemas/ScanCheckResult"
        observations:
          type: array
          items:
            $ref: "#/components/schemas/ScanObservation"
        runtime_contract:
          $ref: "#/components/schemas/RuntimeContract"
        issues:
          type: array
          items:
            $ref: "#/components/schemas/PublicScanIssue"
    RuntimeContract:
      type: object
      properties:
        status:
          type: string
          enum:
            - pass
            - warn
            - fail
            - unknown
        brand:
          type: string
          example: cybexo
        loader_present:
          type: boolean
        loader_url:
          type: string
          format: uri
        loader_id:
          type: string
          example: cybexo-cmp
        settings_id:
          type: string
        install_surfaces:
          type: array
          items:
            type: string
            examples:
              - direct
              - gtm
              - wordpress
              - shopify
        source_markers:
          $ref: "#/components/schemas/SourceMarkers"
        artifact:
          $ref: "#/components/schemas/LoaderArtifact"
        checks:
          type: array
          items:
            $ref: "#/components/schemas/RuntimeCheck"
      additionalProperties: true
    SourceMarkers:
      type: object
      properties:
        wordpress:
          type: boolean
        google_tag_manager:
          type: boolean
        cybexo_loader_url:
          type: boolean
        cybexo_loader_id:
          type: boolean
        cybexo_cdn_url:
          type: boolean
        cybexo_api_url:
          type: boolean
        cybexo_wordpress_plugin:
          type: boolean
        cybexo_preferences_marker:
          type: boolean
        cybexo_runtime_storage:
          type: boolean
        cybexo_event_namespace:
          type: boolean
        cybexo_debug_namespace:
          type: boolean
      additionalProperties:
        type: boolean
    LoaderArtifact:
      type: object
      properties:
        fetched:
          type: boolean
        status:
          type: integer
        version:
          type: string
        build:
          type: string
        channel:
          type: string
        commit:
          type: string
        built:
          type: string
          format: date-time
        payload_sha256:
          type: string
        artifact:
          type: string
      additionalProperties: true
    RuntimeCheck:
      type: object
      required:
        - key
        - status
        - detail
      properties:
        key:
          type: string
        status:
          type: string
          enum:
            - pass
            - warn
            - fail
            - unknown
        detail:
          type: string
        evidence:
          type: string
      additionalProperties: true
    PageAssessment:
      type: string
      enum: [pass, fail, inconclusive, not_applicable]
      description: Assessment of available evidence. Inconclusive and not_applicable are not passes. Static source alone cannot prove executed consent behavior.
    ScanCoverage:
      type: object
      required: [attempted, tested, failed, excluded, discovered, limit_reached, complete, scope]
      description: Bounded crawl coverage. Counts can overlap and discovered is not a complete website inventory.
      properties:
        attempted:
          type: integer
          minimum: 0
          description: Page fetches attempted.
        tested:
          type: integer
          minimum: 0
          description: HTML pages checked.
        failed:
          type: integer
          minimum: 0
          description: Page acquisition failures, including unsuccessful HTTP responses.
        excluded:
          type: integer
          minimum: 0
          description: URLs excluded by crawl rules or non-HTML responses; some are excluded before fetching.
        discovered:
          type: integer
          minimum: 0
          description: Bounded known URL count, not all website landing pages.
        limit_reached:
          type: boolean
          description: A page, discovery, time, request, or response-size limit interrupted the scan.
        complete:
          type: boolean
          description: Discovery and processing finished within this scan scope without recorded failures or exclusions. Does not establish whole-site coverage or runtime verification.
        scope:
          type: string
          const: static_html
    ScanCheckResult:
      type: object
      required: [check_key, status, confidence, source]
      properties:
        check_key:
          type: string
        status:
          type: string
          enum: [pass, fail, warn, unknown]
        confidence:
          type: number
          minimum: 0
          maximum: 1
        details:
          type: string
        source:
          type: string
          enum: [static_html, runtime_observation]
          description: Public scans currently use static_html. A runtime observation describes the observed behavior only, not certification or all effective consent state.
    ScanObservation:
      type: object
      required: [key, status, detail, source]
      properties:
        key:
          type: string
        status:
          type: string
          enum: [observed, not_observed, not_evaluated]
        detail:
          type: string
        source:
          type: string
          const: static_html
    PublicScanIssuePage:
      type: object
      required: [url, issues]
      properties:
        url:
          type: string
          format: uri
        issues:
          type: array
          items:
            $ref: "#/components/schemas/PublicScanIssue"
    PublicScanIssue:
      type: object
      required: [type]
      properties:
        type:
          type: string
          description: Finding or acquisition code.
        details:
          type: string
        missing:
          type: array
          items:
            type: string
        offending:
          type: array
          items:
            type: string
      additionalProperties: true
    ResponseMeta:
      type: object
      required:
        - request_id
        - timestamp
      properties:
        request_id:
          type: string
        timestamp:
          type: string
          format: date-time
        rate_limit:
          $ref: "#/components/schemas/RateLimit"
      additionalProperties: true
    RateLimit:
      type: object
      properties:
        limit:
          type: integer
        remaining:
          type: integer
      additionalProperties: true
    ErrorResponse:
      type: object
      required:
        - ok
        - error
        - meta
      properties:
        ok:
          type: boolean
          const: false
        error:
          oneOf:
            - $ref: "#/components/schemas/ErrorObject"
            - type: string
        reason:
          type: string
        retry_after_seconds:
          type:
            - integer
            - "null"
        meta:
          $ref: "#/components/schemas/ResponseMeta"
      additionalProperties: true
    ErrorObject:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
        message:
          type: string
      additionalProperties: true
